- 系统编程
- 错误处理
- 检测错误
- 函数的返回值
- errno
- 0: 没有错误
- 非0:发生了某种类型的错误
- 打印错误信息
- strerror(errnum)
- 作用:将整数解析成文本的错误信息
- errnum
- 0: Success
- 非0: 具体的错误信息
- perror(prefix)
- 比较死板
- fprintf
- 比较繁琐
- error
- status
- 0: 不退出,不执行exit()
- 非0:打印错误信息后,执行 exit(status)
- errnum
- 0
- 非0:在最后输出 strerror(errnum)
- 格式串
- 自定义输出格式
- status
- strerror(errnum)
- 检测错误
- 如何查看man手册
- 手册编号
- 1:shell命令
- 2:系统调用
- 3:库函数
- NAME
- 一句话简单地介绍函数、命令的作用
- SYNOPSIS
- 头文件
- 函数的声明
- 指针类型的参数
- 有const
- 传入参数
- 函数不会修改指针指向的内容
- 传入参数
- 无const
- 传入传出参数
- 函数会修改指针指向的内容
- 传入传出参数
- 有const
- 指针类型的返回值
- 栈
- 自动存储期限
- 静态区
- 静态存储期限
- 堆
- 动态存储期限
- 调用者负责free
- 动态存储期限
- 栈
- 指针类型的参数
- RETURN VALUE
- 成功
- 失败
- DESCRIPTION
- 带着问题去看
- 看地懂得就看,看不懂得就不看
- 手册编号
- 文件
- 目录
- getcwd
- 作用:获取当前工作目录的绝对路径
- 参数
- buf
- size
- 返回值
- 成功:当前工作目录
- 失败:NULL,并设置errno
- 特例
- chdir
- 作用:修改当前工作目录
- 当前工作目录是进程的属性!
- 参数
- path
- 返回值
- 成功:0
- 失败:-1,并设置errno
- 作用:修改当前工作目录
- mkdir
- 作用:创建目录
- 参数
- path
- mode
- 目录的实际权限:mode & ~umask
- 返回值
- 成功:0
- 失败:-1,并设置errno
- rmdir
- 作用:删除空目录
- 参数
- path
- 返回值
- 成功:0
- 失败:-1,并设置errno
- 目录流
- opendir
- 作用:打开目录流
- 参数
- path
- 返回值
- 成功:返回指向目录流的指针 (DIR*)
- 失败:NULL,并设置errno
- closedir
- 作用:关闭目录流
- 参数
- DIR* stream
- 返回值
- 成功:0
- 失败:-1,并设置errno
- readdir
- 作用:读取下一个目录项
- 参数
- DIR* stream
- 返回值
- 成功
- 返回指向下一个目录项的指针
- struct dirent
- d_name
- d_ino
- d_type
- DT_REG
- DT_DIR
- DT_BLK
- DT_CHR
- DT_LNK
- DT_SOCK
- DT_FIFO
- DT_UNKNOWN
- struct dirent
- 如果读到流的末尾,返回NULL,不设置errno
- 返回指向下一个目录项的指针
- 失败:NULL,设置errno
- 成功
- 递归处理目录
- a. 递归打印目录 (tree)
- b. 递归复制目录 (cp -r dir1 dir2)
- c. 递归删除目录 (rm -r dir)
- opendir
- getcwd
- 文件描述符
- 内核管理文件的数据结构
- fd
- 文件描述符表
- 打开文件
- flags
- 文件的位置
- vnode的指针
- 引用计数
- vnode
- inode的副本
- 设备号
- inode
- 为什么这么设计
- 安全
- 简单方便
- 提供了统一的操作方式
- 通用性
- 操作
- open
- 作用:打开文件描述符
- 参数
- path
- flags
- O_RDONLY
- O_WRONLY
- O_RDWR
- O_CREAT
- O_EXCL
- O_TRUNC
- O_APPEND
- mode
- 如果flags里设置了 O_CREAT, 就需要传入文件的创建权限
- 返回值
- 成功:文件描述符 (最小可用的文件描述符)
- 失败:-1, 并设置errno
- close
- 作用:关闭文件描述符
- 参数
- fd
- 返回值
- 成功:0
- 失败:-1,并设置errno
- read
- 作用:读文件描述符
- 参数
- fd
- buf
- size
- 返回值
- 成功:返回实际读取的字节数目。如果返回值为 0,表示读到了文件的末尾。
- 失败:-1,并设置errno
- write
- 作用:写文件描述符
- 参数
- fd
- buf
- size
- 返回值
- 成功:返回实际写入的字节数目
- 失败:-1,并设置errno
- lseek
- 作用:移动文件位置
- 参数
- fd
- offset
- whence
- SEEK_SET
- SEEK_CUR
- SEEK_END
- 返回值
- 成功:移动后文件的位置
- 失败:-1,并设置errno
- fsync
- 作用:将数据写回到磁盘
- 参数
- fd
- 返回值
- 成功:0
- 失败:-1,并设置errno
- ftruncate
- 作用:截断文件
- 参数
- fd
- length
- length < 原文件大小,截断的数据会丢失
- length > 原文件大小,扩充的部分会填入空字符 (‘\0’)
- 返回值
- 成功:0
- 失败:-1,并设置errno
- fstat
- 作用:查看文件的元数据
- 参数
- fd
- struct stat* statbuf
- 传出参数,用来存储文件的元数据
- struct stat
- st_ino
- st_size
- st_mode
- st_blocks
- st_nlinks
- ……
- struct stat
- 传出参数,用来存储文件的元数据
- 返回值
- 成功:0
- 失败:-1,并设置errno
- dup
- 作用:复制文件描述符
- 参数
- oldfd
- 返回值
- 成功:新的文件描述符 (最小可用的文件描述符)
- 失败:-1,并设置 errno
- 缺陷
- 不能指定 newfd
- close(); dup() 之间有一个时间窗口,可能会引发竞态条件,导致并发问题。
- dup2
- 作用:复制文件描述符
- 参数
- oldfd
- newfd
- 返回值
- 成功:新的文件描述符
- 失败:-1,并设置 errno
- 内存映射 I/O
- mmap
- 作用:将文件的内容直接映射到进程的虚拟内存空间
- 参数
- addr
- 一般填 NULL,由内核指定起始位置
- length
- prot
- PROT_NONE
- PROT_READ
- PROT_WRITE
- PROT_EXEC
- flags
- MAP_PRIVATE
- 特点
- 写时复制
- 对映射区的修改,不会写回到底层文件
- 作用:用文件的内容初始化内存空间
- 特点
- MAP_SHARED
- 特点
- 对映射区的修改,会写回到磁盘
- 进程共享
- 作用
- 内存映射 I/O (零拷贝), 一般适用于大文件
- IPC
- 特点
- MAP_PRIVATE
- fd
- offset
- 一定是页的整数倍
- addr
- 返回值
- 成功:映射区的起始地址
- 失败:MAP_FAILED,并设置 errno
- munmap
- 作用:解除映射
- 参数
- addr
- length
- 返回值
- 成功:0
- 失败:-1,并设置errno
- mmap
- open
- 内核管理文件的数据结构
- 目录
- 进程
- 理论模型
- 什么是进程
- 目的:压榨计算机的资源
- 概念
- 用户:程序的动态执行过程
- 内核:正在执行的任务,计算机的负载
- 进程是计算机分配资源的最小单位
- struct proc
- 内存资源
- 上下文
- CPU的状态
- 外部设备
- ofile
- 当前工作目录
- 父进程
- 状态
- ….
- 实现
- 底层机制
- 如何切换进程
- 方案一
- 简单,快速
- 不安全
- 没有控制权
- 方案二
- 引入了CPU的模态
- 用户态
- 不能访问非法内存空间
- 不能执行特权指令
- 内核态
- 问题:应用程序如何执行特权指令
- 系统调用
- 陷阱表
- 用户态
- 缺点:操作系统不能主动获取控制权
- 引入了CPU的模态
- 方案三
- 引入时钟设备
- 每隔xms会触发一次时钟中断
- 引入时钟设备
- 方案一
- 如何切换进程
- 上层策略
- 调度算法
- 底层机制
- 上下文切换的开销
- 保存寄存器
- 恢复寄存器
- 高速缓存失效
- TLB的失效
- 页表的缓存
- 什么是进程
- API
- 基本操作 (原语)
- 获取进程的标识
- getpid
- getppid
- 创建进程
- fork
- 原理
- 复制父进程的 PCB,并修改其中的某些信息
- 复制父进程的页表
- 写时复制 (copy on write)
- ……
- 关键点
- 父子进程都是从 fork() 返回点,开始执行的
- 一次调用,两次返回
- 当fork()返回后,到底是父进程先执行,还是子进程先执行,这是不确定的。
- 父子进程都是从 fork() 返回点,开始执行的
- 返回值
- 成功
- 父进程:子进程的pid
- 子进程:0
- 失败:-1,并设置 errno
- 成功
- 父子进程资源共享
- 代码段
- 共享
- 栈,堆,数据段
- 私有
- 用户态缓冲区
- 私有
- 面试题
- 私有
- 文件描述符列表
- 私有
- 打开文件
- 共享
- 代码段
- 原理
- fork
- 终止进程
- 正常终止
- 从 main 函数返回
- exit()
- exit()
- 按和注册相反的顺序执行退出处理函数
- atexit
- 参数
- void (*function) (void)
- 返回值
- 成功:0
- 失败:非0
- 局限性
- 不能获取退出状态
- 没有参数
- 参数
- on_exit
- 参数
- void (function) (int status, void arg)
- void* args
- 返回值
- 成功:0
- 失败:非0
- 参数
- 刷新用户态缓冲区
- 调用 _exit()
- _exit()
- 参数
- status
- 只用了后8位,表示退出状态。0 表示执行成功,非0表示执行失败。
- status
- 参数
- 从 main 函数返回
- 异常终止
- abort()
- 给自己发送 SIGABRT 信号
- 发送信号
- abort()
- 正常终止
- 监控子进程的状态
- wait
- 作用:监控子进程是否终止;如果没有子进程终止,会一直阻塞。
- 参数
- int* status
- 传出参数,用来接收子进程的终止状态信息
- WIFEXITED
- 判断子进程是否正常终止
- WEXITSTATUS
- 获取子进程的退出状态
- WIFSIGNALED
- 判断子进程是否异常终止
- WTERMSIGNAL
- 获取导致子进程异常终止的信号
- WCOREDUMP
- 非标准
- WIFEXITED
- 传出参数,用来接收子进程的终止状态信息
- int* status
- 返回值
- 成功:终止子进程的pid
- 失败:-1,并设置erno
- 局限性
- 如果没有子进程终止,会一直阻塞。
- 不能指定监控哪一个子进程
- 只能监控子进程是否终止
- waitpid
- 作用:监控子进程的状态 (终止、停止、继续)
- 参数
- pid
0: 监控指定的子进程
- =0:监控同进程组的子进程
- =-1:监控任意的子进程
- <-1: 监控进程组 ID 为 |pid| 的子进程
- int* status
- 传出参数,用来接收子进程的终止状态信息
- WIFEXITED
- 判断子进程是否正常终止
- WEXITSTATUS
- 获取子进程的退出状态
- WIFSIGNALED
- 判断子进程是否异常终止
- WTERMSIGNAL
- 获取导致子进程异常终止的信号
- WCOREDUMP
- 非标准
- WIFSTOPPED
- 判断子进程是否停止
- WSTOPSIG
- 获取导致子进程停止的信号
- WIFCONTINUED
- 判断子进程是否继续执行
- WIFEXITED
- 传出参数,用来接收子进程的终止状态信息
- options
- WONHANG
- 不阻塞
- WUNTRACE
- 监控停止状态
- WCONTINUED
- 监控继续状态
- WONHANG
- pid
- wait
- 执行程序
- execve
- 原理
- 清除进程的代码段、数据段、堆、栈…
- 加载新的可执行程序,设置代码段和数据段
- 从新的可执行程序main函数的第一行开始执行
- 参数
- pathname
- 可执行程序的路径
- char* argv[]
- 命令行参数
- argv[0]: 可执行程序的名字
- 以NULL结尾
- 命令行参数
- char* envp[]
- 环境变量
- 以 NULL 结尾
- 环境变量
- pathname
- 返回值
- 成功:不返回
- 失败:-1,并设置 errno
- 原理
- exec函数簇
- l: list
- 命令行参数以可变长参数的形式指定
- v: vector
- 命令行参数以数组的形式指定
- e: environment
- 重新设置环境变量
- p: PATH
- 根据PATH环境变量查找可执行程序
- l: list
- system
- 作用:执行任意的 shell 命令
- 原理:fork()一个子进程,让子进程执行 “sh -c command”
- 参数
- command
- 要执行的命令
- command
- 惯用法
- fork()
- 子进程执行新的可执行程序
- 父进程等待子进程结束
- execve
- 获取进程的标识
- IPC
- 管道
- 概念:内核管理的数据结构,用于进程之间通信
- 特性
- 字节流
- 数据是没有边界的
- 接收的顺序就是发送的顺序
- 不能够用 lseek() 移动位置
- 半双工
- 管道的容量是有限的
- 如果管道已经满了,写管道会引发阻塞
- 读管道
- 如果管道里面没有数据,读管道会引发阻塞
- 字节流
- pipe
- 作用:创建管道
- 参数
- int fields[2]
- 传出参数:fields[0]会关联到管道的读端;fields[1]会关联到管道的写端
- int fields[2]
- 返回值
- 成功:0
- 失败:-1,并设置errno
- 惯用法
- 先 pipe
- 再 fork
- 父进程关闭管道的一端;子进程关闭管道的另一端
- 限制
- 只能用于相关进程之间通信,往往是父子进程
- fifo
- 作用:创建有名管道 (fifo)
- 命令:mkfifo [-m mode] 名字
- mkfifo
- 参数
- pathname
- mode
- 返回值
- 成功:0
- 失败:-1,并设置errno
- 参数
- 用于任意两个进程之间通信
- 信号 (signal)
- 异步的事件通知机制。是进程感知外部世界的一种很重要的方式。
- 特征
- 异步
- 进程不知道什么时候会收到信号,但一旦收到信号,会立刻响应信号。
- 不稳定
- 处于 pending 状态的信号,可能发生丢失
- 语义不明确
- 不同操作系统信号的语义不尽相同
- 异步
- 事件源
- 用户
- ctrl + c
- SIGINT
- ctrl + z
- SIGTSTP
- ctrl + \
- SIGQUIT
- ctrl + c
- 硬件
- 执行了非法的指令
- SIGILL
- 访问非法的内容
- SIGSEGV
- 除0异常
- SIGFPE
- 执行了非法的指令
- 软件
- 子进程的状态发生修改
- SIGCHLD
- 写破损的管道
- SIGPIPE
- 调用 abort()
- SIGABRT
- 子进程的状态发生修改
- 用户
- 信号的执行流程
- 响应信号
- 默认处置
- Term
- Core
- Stop
- Cont
- Ign
- 自行处置
- 注册信号处理函数
- 默认处置
- API
- 注册信号处理函数
- signal
- 参数
- signo
- handler
- typedef void (*sighandler_t) (int)
- SIG_DFL
- SIG_IGN
- 自定义处理函数
- typedef void (*sighandler_t) (int)
- 返回值
- 成功:旧的信号处理函数
- 失败:SIG_ERR
- 参数
- signal
- 发送信号
- kill
- 参数
- pid
0: 给进程ID为pid的进程发送信号
- =0: 给同进程组的进程发送信号
- =-1:给权限允许的所有进程发送信号
- <-1: 给进程组 id 为 |pid| 的进程发送信号
- signo
- pid
- 返回值
- 成功 (至少发送了一次信号):0
- 失败:-1,并设置 errno
- 参数
- raise
- kill(getpid(), signo)
- kill
- 注册信号处理函数
- 套接字 (socket)
- 共享内存 + 信号量 (semaphor)
- 消息队列
- 管道
- 基本操作 (原语)
- 理论模型
- I/O多路复用
- 最佳实践:一个执行流程最多只能有一个阻塞点
- 作用:同步的I/O事件监听机制
- API
- select
- 参数
- nfds
- 监听的最大文件描述符 + 1
- readfds
- 传入:要监听读事件的文件描述符集合
- 传出:读事件已就绪的文件描述符集合
- writefds
- 传入:要监听写事件的文件描述符集合
- 传出:写事件已就绪的文件描述符集合
- exceptfds
- 传入:要监听看异常事件的文件描述符集合
- 传出:异常事件已就绪的文件描述符集合
- timeout
- 传入:超时时间
- 传出:剩余的时间
- NULL: 一直阻塞
- {0, 0}:不阻塞
- nfds
- 返回值
- 成功
- 返回就绪的 I/O 事件个数;如果超时,返回0
- 失败
- -1,并设置errno
- 成功
- 缺陷
- 监听的文件描述符个数是受限的
- 轮询fd_set,已判断哪些文件描述符就绪
- 参数
- poll
- epoll
- select
- 线程
- 什么是线程
- 进程的一条执行流程,它是CPU调度的最小单位
- 为什么引入线程
- 相对进程而言,线程是轻量级的
- 进程之间通信需要打破隔离的壁障,线程之间通信的代价几乎没有
- 线程之间切换,不会导致 CPU 高速缓存和 TLB 的失效
- API
- 基本操作 (控制原语)
- 获取线程的标识
- pthread_self
- 创建线程
- pthread_create
- 参数
- tid: 传出参数,用来接收新线程的 ID
- attr: 线程的属性,一般填NULL,表示采用默认属性
- void* (start_routine) (void): 线程的入口函数
- arg: 线程入口函数的实参
- 返回值
- 成功:0
- 失败:返回错误码,不设置errno
- 参数
- pthread_create
- 终止线程
- 进程终止,会导致该进程的所有线程终止
- 从线程入口函数返回
- 调用 pthread_exit()
- 响应 pthread_cancel()
- 退出线程
- pthread_exit
- 参数
- void* retval
- 参数
- pthread_exit
- 连接线程
- pthread_join
- 参数
- tid
- void** retval
- 传出参数,用来接收线程的返回值
- 如果线程是因为响应取消请求而终止,线程会返回 PTHREAD_CANCELLED
- 传出参数,用来接收线程的返回值
- 返回值
- 成功:0
- 失败:返回错误码,不设置errno
- 参数
- pthread_join
- 分离线程
- pthread_detach
- 参数
- tid
- 处于游离态的进程不可以被 join, 也不能重新恢复到 joinable 状态
- 参数
- pthread_detach
- 发送取消请求
- pthread_cancel
- 参数
- tid: 目标线程
- 目标线程是否响应取消请求,以及何时响应
- 取消状态
- ENABLE (默认)
- DISABLE
- 取消类型
- DEFFERED (默认)
- 取消点
- ASYNCHROUNS
- 任意时候
- DEFFERED (默认)
- 取消状态
- 参数
- pthread_cancel
- 清理线程
- 线程的清理函数栈
- pthead_cleanup_push
- 参数
- void (routine) (void)
- void* arg
- 参数
- pthread_cleanup_pop
- 参数
- execute
- 0: 不执行
- 非0:执行
- execute
- 参数
- pthead_cleanup_push
- 执行时机
- 响应取消请求
- 调用 pthread_exit()
- 调用 pthread_cleanup_pop(非0)
- 注意:从线程入口函数返回,不执行!
- 线程的清理函数栈
- 获取线程的标识
- 同步
- 竞态条件
- 程序的运行结果 (状态), 取决于程序的调度情况
- 原因
- 多线程
- 共享资源 (数据、文件…)
- 至少有一个线程会修改共享资源
- 并发问题
- 竞态条件
- 同步:避免坏的调度的发生
- 互斥地访问资源
- 锁机制
- 互斥锁
- 用法
- 上锁
- 执行临界区的代码 (对共享资源的访问)
- 释放锁
- API
- 初始化
- 静态初始化
- PTHREAD_MUTEX_INITIALIZER
- 动态初始化
- pthread_mutex_init
- 静态初始化
- 上锁
- pthread_mutex_lock
- pthread_mutex_trylock
- pthread_mutex_timedlock
- 释放锁
- pthread_mutex_unlock
- 销毁
- pthread_mutex_destroy
- 初始化
- 用法
- 自旋锁
- 读写锁
- …
- 互斥锁
- CAS (无锁编程)
- 锁机制
- 等待某个条件成立
- 条件变量 (提供了等待唤醒机制)
- 条件不成立: 等待
- 条件成立:被唤醒 (执行)
- API
- 初始化
- 静态初始化
- PTHREAD_COND_INITIALIZER
- 动态初始化
- pthread_cond_init
- 静态初始化
- 等待
- pthread_cond_wait(&cond, &mutex)
- 释放锁
- 陷入阻塞状态
- 当pthread_cond_wait返回,当前线程一定再一次获取了锁
- 注意事项:返回时,条件不一定成立!存在虚假唤醒现象。
- pthread_cond_timedwait(&cond, &mutex, abstime)
- pthread_cond_wait(&cond, &mutex)
- 唤醒
- pthread_cond_signal(&cond)
- pthread_cond_broadcast(&cond)
- 销毁
- pthread_cond_destroy
- 初始化
- 条件变量 (提供了等待唤醒机制)
- 互斥地访问资源
- 竞态条件
- 生产者消费者模型
- 生产者
- 队列不满
- 生产商品,入队列;队列不空,唤醒消费者
- 队列满了
- 等待
- 队列不满
- 消费者
- 队列不空
- 消费商品,出队列。队列不满,唤醒生产者
- 队列空了
- 等待
- 队列不空
- 阻塞队列
- 生产者
- 基本操作 (控制原语)
- 什么是线程
- 错误处理