进程控制(linux)
进程控制
- 1、进程创建
- 1.1 fork
- 写实拷贝
- 2、进程终止
- 3、进程等待
- 3.1 进程等待的必要性
- 3.2 进程等待的方法
- 4、进程程序替换
1、进程创建
1.1 fork
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
》分配新的内存块和内核数据结构给子进程
》将父进程部分数据结构内容拷贝至子进程
》添加子进程到系统进程列表当中
》fork返回,开始调度器调度
fork常规用法
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
写实拷贝
1、之前我们谈过这里父子代码共享,数据不修改,也是共享的,为什么?
数据不一定修改,就算修改大概率也只会修改很小的部分;
2、操作系统怎么知道我们要写时拷贝了?
当程序要创建子进程时,父进程就会将当前页表的执行权限全部改为只读,当子进程数据需要修改时,操作系统就会马上识别到我们当前操作是对只读区域进行修改(触发系统错误),然后系统进行缺页中断进行系统检测,然后系统判断发生写时拷贝,然后系统再申请内存、发生拷贝、、、、
3、上面我们说只改变了一部分数据,那为什么还要重新全部拷贝一份数据?
写入操作!=对目标区域的覆盖操作
2、进程终止
1、main函数返回值—返回给父进程/系统
2、进程退出码–代码错误原因(不同数字表示不同原因)
3、退出码可以由系统的退出信息来确定,也可以自定义
4、(语言级别)exit让代码在任何地方结束(进程结束),(系统级别)_exit(两者区别在刷新缓冲区的问题)
exit底层就是_exit
5、我们之前的缓冲区概念是语言级别的,跟操作系统无关—c/c++
3、进程等待
3.1 进程等待的必要性
1.之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法
杀死一个已经死去的进程。
2.最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,
或者是否正常退出。
3.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
3.2 进程等待的方法
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:pid:Pid=-1,等待任一个子进程。与wait等效。Pid>0.等待其进程ID与pid相等的子进程。status:**WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)****WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)**options:WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退
出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。
获取子进程status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
位):
知识点
1、假如我们的程序,出现错误出现根本没跑完的情况,例如(1/0等),其实计算机是不怕的,因为操作系统会通过信号干掉这个进程 ----(kill -l 查看所有错误信号)
2、进程退出时会记录自己的退出信号
3、我们只能通过系统调用来获取进程退出信息
4、进程出现异常时,操作系统会使用信号终止进程
5、父进程能够知道子进程,子进程跑没跑完由退出码决定,有没异常由信号决定
阻塞非阻塞的问题
4、进程程序替换
1、上面我们其实谈论的都是子进程执行父进程的代码,那要是我们想要让子进程执行新的程序呢?这就是进程程序替换
2、进程替换本质上是我们使用excel调用,直接将当前进程的代码和数据直接覆盖掉,并且执行替换后的程序,本质上就是加载,所以并没有创建新的进程
3、替换后的程序既然可以是系统命令,那能不能是我们自己的程序呢?
4、execl成功时没有返回值,当失败时返回-1,所以这一系列函数,一旦出现返回,必定执行失败
5、之前我们说过冯洛伊曼结构中,想要将一个程序运行,就必须将它加载到内存中,也就是将该程序的代码和数据加载到内存,execl的操作本质上就是一个加载的操作
6、我们上面已经知道了当我们使用execl时,该进程的代码和数据就已经全部被替换,那要是我们让子进程去执行execl这一操作,父进程仍然完成自己的操作,例如命令行提示,这不就是一个简单的shell嘛
7、所以linux下所有的程序,全都是fork+execl来执行的