2025年5月29日 12:07 周四概述
#
从程序员的角度来讨论这个话题
标准的Unix库:printf和scanf
低级IO,直接面向操作系统

Unix I/O概括
#
用文件(一堆二字节序列)来描述很多抽象的事物,比如I/O设备,打字机;网络连接(套接字),消息是写入套字节传送,读取套字节接收;

文件还有个属性:读取位置;网络套接字不允许在时间上跳转,只能在数据包进入时对其进行读或写
文件类型
#
文件、目录、套接字(作为发送和接收)、应用程序之间接收发送(视频不讲)
符号链接(多指针指向同一文件等)

普通文件
#
- 有些应用程序会区分文本文件和二进制文件(不是在操作系统级别区分,而是在更高级别)
- 主要区别:文本文件只有标准的ASCII字符或者对非英文字符编码的Unicode字符
- 二进制文件是图像或实际目标代码或音视频文件等所有其他的文件(这类文件有一个字节序列直接是某种形式编码的数字)
- 文本文件:新行 符号,0xa;windows:\r\n
0xd 0xa,Linux:\n 0xa

目录
#


目录名
#

打开文件
#
- 以指定方式打开文件,这里会返回一个文件操作符
- O_RDONLY(什么模式下访问文件)


- 同时打开的文件数量有限

每次进行系统调用时都应该检查返回值,看看是否存在错误,并采取适当方法处理错误
- 有几种和终端关联的文件:标准输入、标准输出、标准错误
关闭文件
#
多线程中容易出错,关闭已经关闭的文件

读文件
#
读入若干字节数(最小是1)
...
2025年5月29日 11:57 周四主旨
#
真正的理解和熟练地编写网络软件
利用与网络编程有关的c语言api
如何建立一个网络系统
客户端-服务端传输
#

网络主机的硬件结构
#

当你发送信息时,实际上是通过向一个叫做网络的虚拟文件写入数据来实现的;而当你想接收数据时,通过读取该文件来实现
2025年5月27日 14:59 周二
2025年5月27日 14:59 周二概述
#
数据表示
目前都是操作整数、长整数、指针(标量数据),而不是聚合形式的数据
数组、结构
在机器内存中的表现方式
数组不过是一堆字节,但是这些字节的集合,是在连续位置上存储的;结构也是如此,作为字节集合来分配的
数组分配
#
x,x+4,x+8这些都是内存地址(机器代码会这么处理)(不是指数组下标)

T A[L];
- 分配足够的存储字节来保存整个数组
- 像对待指针一样对待标识符A

对于找val[4]这个元素,汇编中,编译器知道指针类型后,会自动写出合适的缩放因子,不需要程序员手动处理
例子
#

访问数组的例子
#

循环处理数组元素
#
ZLEN=5,cmpq b,a
(比较的是a和b),jbe 低于或等于;这里就是说如果%rax 小于或等于4,则跳转到.L4程序解释:先比较 %rax和$4,如果小于或等于0,则跳转到.L4
addl $1,(%rdi,%rax,4)
,首先从内存中读取原始值,进行加法运算,然后将结果放回内存中

数组和指针之间真正区别
#
当你在c中声明一个数组时,你既在分配空间,分配某个位置的空间,同时,也在创建一个允许在指针运算使用的数组名称。当你只是声明一个指针时,你所分配的只是指针本身的空间,没有给他指向的东西分配空间

例子2
#
- 【从内往外读】A3是一个指针,指向(由三个int组成的数组)
- A3可能是一个空指针,即 *A3可能为空

- 优先级排序:(),[],
*
, - A4是三个元素的数组,这些元素是指针,而这些指针指向int
多维数组
#
2025年5月26日 06:54 周一概述
#
procedures:面向过程编程中的函数、过程,或者,面向对象中的方法
abi(应用程序二进制接口): application binary interface。它要求所有Linux程序、编译器、操作系统、系统所有不同部分,都需要对如何管理机器上的资源有一些共同的理解。机器程序级别的接口。
- ABI关注的是二进制代码如何在实际硬件上执行和交互
- Windows、Linux 和 macOS 的 ABI(应用程序二进制接口)在核心设计上有显著差异,这导致它们的二进制程序通常无法直接跨平台运行。
控制(函数调用)
#

- 将控制权转移给另一个函数,且函数执行完毕后返回控制权
- 如何传参,以及被调用函数如何将结果传给调用函数
- 被调用函数中的局部变量分配问题
- 尽量减小过程调用中的开销(对于程序员,良好的编程习惯: 尽量多功能多函数)
控制权转移
#
- stack(栈):其实只是普通内存的一块区域
- 对于汇编层面的程序员来说,内存只是一个巨大的字节数组
- 用栈来管理过程调用与返回的状态
- 利用栈后进先出的特点:调用函数时需要一些信息,从函数返回需要丢掉一些信息。

pop
#
这是三个操作复合成一个操作(先读取,后递减栈指针,最后存储值)

栈的释放只是移动栈指针,没有其他特殊操作。数据还在内存中,只不过不是栈的一部分了。
push
#
- 被push的参数,也必须是寄存器,或立即数。(因为不允许直接从内存传递数据到内存)
- 这也是个复合操作:获取操作数,递减栈指针(而且,内存读一个数据,数据的地址指的是它的(最)低地址),最后写入值

例子1
#
- call和ret引用了栈的思想
- 局部变量的存取通过 %rsp 偏移(%rsp-8,%rsp+8之类)实现,不会直接修改 %rsp

调用前将返回地址压入栈中,然后去执行函数,调用后从栈顶(%rsp)取出调用函数前存的下一条指令的地址,并将%rip(程序计数器[Program Counter, PC]设置为该值
注意:在函数返回前,需通过 add $N, %rsp 或 leave 指令(相当于 mov %rbp, %rsp + pop %rbp)恢复[很重要!!] %rsp 到调用前的状态。
...
2025年5月24日 17:02 周六概述
#
本节主要学习如何控制机器级别指令的执行顺序
基本条件语句(条件码,条件分支)、循环(循环)、switch语句
ProcessorState
#

1.条件码
#
- CF(无符号):进位(溢出)
- ZF:刚才的计算结果为零
- SF(有符号):结果的最高有效位为1(负值)
- OF(有符号):进位(溢出)

这些标志一般情况下会被忽视,当进行条件操作时会被关注
显式设置1
#

显式设置2
#
testq b,a相当于计算 a&b

如果testq a,a。那么只有当a为零时,ZF才会被设置
读取条件码
#
根据条件码的某种组合,将一个字节设置成0或1
- 最简单的是,设置ZF条件码的值
sete %al
(ZF是什么就设置成什么)

直接设置寄存器的最低字节
#
该操作不会影响其他7个字节

条件相关例子
#

- %eax是%rax的低四位
- mov指令,从单字节到四字节的零扩展指令
- 前两步骤保证了低四字节的零扩展;x86-64保证:任何计算结果是32位(四字节)的计算,会把寄存器其余32位设置为0(高四字节)。但是,如果是单字节,或双字节,只会影响这个单字节或双字节的结果。
跳转指令
#
包括无条件跳转/有条件跳转

传统分支例子
#
jle .L4 表示如果条件码结果表示是 y <=x,则跳转到 .L4

...
2025年1月19日 09:34 周日概述
#
计算机为了运行程序,会执行一串独立的指令
两种形式的机器语言:
- 在计算机上运行的实际目标代码(字节,二进制01)
- 汇编代码(编译器的目标,文本格式)
不写汇编,但需要学习:
- 编译器产生的结果(汇编及目标代码)与文本代码关联
- 低级代码(汇编、二进制)如何实现高级别程序构造
- 过程函数、结构体、数组在机器语言中的实现
课程选择64位版本的Intel指令集
为什么叫x86处理器16位微处理器(也称CISC复杂
,与之对应的是RISC精简
),因为一开始的处理器是8086,接着8286,8386…
需要更加关注–gcc编译生成的代码长什么样
x86进化
#

多核处理器
#


另一个公司:AMD

教学范围
#

哪些处理器
#
- ARM(AcornRISCMachine),比x86机器功耗更低
- x86
定义
#
- 指令集
- 寄存器:非常小的内存位置
- 机器指令

(无法直接操作缓存(没有这个概念),是机器级别的概念)
将c源代码转换成目标文件
#

汇编简介
#

gcc -Og -S sum.c #-Og 调试级别的优化过的(英文字母大写O); -S 生成汇编

带点的这些,是给调试器用的(用来定位);也有一些是给链接器用(告诉它这是个全局定义的函数)
数据类型
#

...