系统编程复习要点

  • 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函数来实现。

允许多个读写端

基本使用

注意事项

  1. 用于创建映射区的文件大小为 0,实际指定非0大小创建映射区,出 “总线错误”。
  2. 用于创建映射区的文件大小为 0,实际制定0大小创建映射区, 出 “无效参数”。
  3. 用于创建映射区的文件读写属性为,只读。映射区属性为 读、写。 出 “无效参数”。
  4. 创建映射区,需要read权限。当访问权限指定为 “共享”MAP_SHARED时, mmap的读写权限,应该 <=文件的open权限。 只写不行。
  5. 文件描述符fd,在mmap创建映射区完成即可关闭。后续访问文件,用地址访问。
  6. offset 必须是 4096的整数倍。(MMU 映射的最小单位 4k )
  7. 对申请的映射区内存,不能越界访问。
  8. munmap用于释放的地址,必须是mmap申请返回的地址。
  9. 映射区访问权限为 “私有”MAP_PRIVATE, 对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上。
  10. 映射区访问权限为 “私有”MAP_PRIVATE, 只需要open文件时,有读权限,用于创建映射区即可。
  11. 保险调用法

mmap总结

  1. 创建映射区的过程中,隐含着一次对映射文件的【读操作】
  2. MAP_SHARED时,要求:映射区的权限应该<=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制
  3. 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭
  4. 特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!!mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。如,400字节大小的文件,在简历映射区时,offset4096字节,则会报出总线错误
  5. munmap传入的地址一定是mmap返回的地址。坚决【杜绝指针++】操作
  6. 文件偏移量必须为4K的整数倍
  7. 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值,表示可以同时访问共享数据区的线程数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值