-
open函数
close函数
阻塞与非阻塞
现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用 sleep 指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在 Linux 内核中,处于运行状态的进程分为两种情况:
正在被调度执行。 CPU 处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。
就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但 CPU 暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。
read函数
write函数
fcntl函数
- fcntl用来改变一个【已经打开】的文件的 访问控制属性
- 重点掌握两个参数的使用, F_GETFL,F_SETFL
lseek函数
修改文件偏移量
传入传出参数
stat函数
link与ulink函数
dup函数和dup2函数
fork函数
获取进程id
父子进程相同内容
- 相同内容
- 不同内容
exec函数族
孤儿进程和僵尸进程
定义
回收子进程
- wait函数
- waitpaid函数
获取子进程退出值和异常终止信号
- 一个进程终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。
- 这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清除掉这个进程。
- 一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell是它的父进程,当它终止时,shell调用wait或者waitpid得到它的退出状态,同时彻底清除掉这个进程。
进程通信
管道通信
允许多个读写端
基本用法
- 管道读写
优缺点
fifo管道
fifo管道可以用于无血缘关系的进程间通信,fifo操作起来像文件。
允许多个读写端
基本用法
文件用于进程通信
mmap函数
存储映射I/O(Memory-mapped I/O)
- 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是从缓冲区中取数据,就相当于读文件中的相应字节。
- 与此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不使用read和write函数的情况下,使地址指针完成I/O操作。
- 使用这种方法,首先应该通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。
允许多个读写端
基本使用
注意事项
- 用于创建映射区的文件大小为 0,实际指定非0大小创建映射区,出 “总线错误”。
- 用于创建映射区的文件大小为 0,实际制定0大小创建映射区, 出 “无效参数”。
- 用于创建映射区的文件读写属性为,只读。映射区属性为 读、写。 出 “无效参数”。
- 创建映射区,需要read权限。当访问权限指定为 “共享”MAP_SHARED时, mmap的读写权限,应该 <=文件的open权限。 只写不行。
- 文件描述符fd,在mmap创建映射区完成即可关闭。后续访问文件,用地址访问。
- offset 必须是 4096的整数倍。(MMU 映射的最小单位 4k )
- 对申请的映射区内存,不能越界访问。
- munmap用于释放的地址,必须是mmap申请返回的地址。
- 映射区访问权限为 “私有”MAP_PRIVATE, 对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上。
- 映射区访问权限为 “私有”MAP_PRIVATE, 只需要open文件时,有读权限,用于创建映射区即可。
- 保险调用法
mmap总结
- 创建映射区的过程中,隐含着一次对映射文件的【读操作】
- 当MAP_SHARED时,要求:映射区的权限应该<=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制
- 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭
- 特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!!mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。如,400字节大小的文件,在简历映射区时,offset4096字节,则会报出总线错误
- munmap传入的地址一定是mmap返回的地址。坚决【杜绝指针++】操作
- 文件偏移量必须为4K的整数倍
- mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
使用案例
- 父子进程
- 无血缘关系进程
- 匿名映射区
信号
信号共性:简单、不能携带大量信息、满足条件才发送。
信号的特质:信号是软件层面上的“中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令。
所有信号的产生及处理全部都是由【内核】完成的。
信号的产生
1. 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\
2. 系统调用产生,如:kill、raise、abort
3. 软件条件产生,如:定时器alarm
4. 硬件异常产生,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
5. 命令产生,如:kill命令
递达:递送并且到达目的进程,直接被内存处理掉。
未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。
信号的处理方式
1. 执行默认动作
2. 忽略(丢弃)
3. 捕捉(调用户自定义处理函数)
信号四要素:1. 编号 2. 名称 3. 事件 4. 默认处理动作
默认动作:
kill函数与kill命令
alarm函数与setitimer函数
第一次信息打印是两秒间隔,之后都是5秒间隔打印一次
信号集操作函数
信号集set函数:
sigset_t set;
自定义信号集。
sigemptyset(sigset_t *set);
清空信号集
sigfillset(sigset_t *set);
全部置1
sigaddset(sigset_t *set, int signum);
将一个信号添加到集合中
sigdelset(sigset_t *set, int signum);
将一个信号从集合中移除
以上函数成功返回0,失败返回-1。
sigismember(const sigset_t *set,int signum);
判断一个信号是否在集合中。
返回值:在返回1, 不在返回0,失败返回-1。
sigprocmask函数:
sigpending函数:
使用案例:
信号捕捉函数
使用案例:
信号捕捉特征
1. 捕捉函数执行期间,信号屏蔽字 由 mask --> sa_mask , 捕捉函数执行结束。 恢复回mask
2. 捕捉函数执行期间,本信号自动被屏蔽(sa_flgs = 0).其他信号不屏蔽,如需屏蔽则调用sigsetadd函数修改
3. 捕捉函数执行期间,被屏蔽信号多次发送,解除屏蔽后只处理一次!
借助信号捕捉回收子进程
SIGCHLD的产生条件:
- 子进程终止时
- 子进程接收到SIGSTOP
- 子进程处于停止态,接收到SIGCONT后唤醒时
一次回调可以回收多个子进程
问题:
- 一次回调只回收一个子进程,同时出现多个子进程死亡时,只会回收累积信号中的一个子进程。
- 有可能父进程还没注册完捕捉函数,子进程就死亡了
解决方法:
- 首先是让子进程sleep,但这个不太科学。在fork之前注册也行,但这个也不是很科学。
- 最科学的方法是在int i之前设置屏蔽,等父进程注册完捕捉函数再解除屏蔽。这样即使子进程先死亡了,信号也因为被屏蔽而无法到达父进程。解除屏蔽过后,父进程就能处理累积起来的信号了。
回收子进程案例:
1. void sys_err(const char *str) {
2. perror(str);
3. exit(1);
4. }
5.
6. void catch_child(int signo) // 有子进程终止,发送SGCHLD信号时,该函数会被内核回调
7. {
8. pid_t wpid;
9. int status;
10. //if((wpid = wait(NULL)) != -1) { //只能执行一次,处理一个信号
11. while((wpid = waitpid(-1, &status, 0)) != -1) { // 循环回收,防止僵尸进程出现.
12. if (WIFEXITED(status))
13. printf("---------------catch child id %d, ret=%d\n", wpid, WEXITSTATUS(status));
14. }
15. return ;
16. }
17.
18. int main(int argc, char *argv[])
19. {
20. pid_t pid;
21. //阻塞
22. sigset_t set;
23. sigemptyset(&set);
24. sigaddset(&set, SIGCHLD);
25. sigprocmask(SIG_BLOCK, &set, NULL); //先对对应的信号进行阻塞
26.
27. int i;
28. for (i = 0; i < 15; i++)
29. if ((pid = fork()) == 0) // 创建多个子进程
30. break;
31.
32. if (15 == i) {
33. struct sigaction act;
34. act.sa_handler = catch_child; // 设置回调函数
35. sigemptyset(&act.sa_mask); // 设置捕捉函数执行期间屏蔽字
36. act.sa_flags = 0; // 设置默认属性, 本信号自动屏蔽
37. sigaction(SIGCHLD, &act, NULL); // 注册信号捕捉函数
38. //解除阻塞
39. sigprocmask(SIG_UNBLOCK, &set, NULL);
40.
41. printf("I'm parent, pid = %d\n", getpid());
42. while (1); //模拟后续逻辑
43.
44. } else {
45. printf("I'm child pid = %d\n", getpid());
46. return i;
47. }
48.
49. return 0;
50. }
守护进程
是一种在后台运行的特殊进程,独立于控制终端,主要负责执行某些任务或等待处理特定事件的发生。
线程
进程:有独立的进程地址空间。有独立的pcb。 分配资源的最小单位。
线程:有独立的pcb。没有独立的进程地址空间。 最小单位的执行。
线程共享资源:
1.文件描述符表
2.每种信号的处理方式
3.当前工作目录
4.用户ID和组ID
5.内存地址空间 (.text/.data/.bss/heap/共享库)
线程独享资源:
1.线程id
2.处理器现场和栈指针(内核栈)
3.独立的栈空间(用户空间栈)
4.errno变量
5.信号屏蔽字
6.调度优先级
获取线程id
创建线程
错误原因在于,子线程如果用引用传递i,会去读取【主线程】里的【i值】,而主线程里的i是动态变化的,不固定。所以,应该采用值传递,不用引用传递。
pthread_exit函数
回到之前说的一个问题,由于主线程可能先于子线程结束,所以子线程的输出可能不会打印出来,当时是用主线程sleep等待子线程结束来解决的。现在就可以使用pthread_exit来解决了。方法就是将return 0替换为pthread_exit,只退出当先线程,不会对其他线程造成影响。
pthread_join函数
回收线程,相当于进程中的waitpid()函数。
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的。
使用案例:
pthread_cancel函数
pthread_detach函数
作用:实现线程分离。
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
使用detach分离线程,分离后的线程会自动回收。
锁
锁函数
pthread_mutex_t mutex;创建锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);初始化
int pthread_mutex_destory(pthread_mutex_t *mutex);
销毁
int pthread_mutex_lock(pthread_mutex_t *mutex);
上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
try锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
解锁
互斥锁使用过程
初始化互斥量:
使用技巧
读写锁
条件变量
条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
主要函数
初始化条件变量
pthread_cond_t cond;创建条件变量
1. 动态初始化:pthread_cond_init(&cond, NULL);
2. 静态初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
条件变量和相关函数wait
在消费者生产者模型中,给消费者加上条件变量+锁,使得消费者放弃持有临界区的锁,阻塞在条件变量上,使得生产者能持有临界区的锁,给临界区添加内容。
信号量
信号量
- 应用于线程、进程间同步。
- 相当于初始化值为 N 的互斥量。 N值,表示可以同时访问共享数据区的线程数。