Linux下的进程控制
1、进程创建
Linux下采用fork创建一个新进程,新进程为原来进程的子进程
#include <unistd.h>
pid_t fork(void);
返回值,出错返回-1,父进程返回子进程的ID,子进程返回0
当一个进程调用fork以后,就会有两个二进制代码相同的进程,自此之后,父子进程分别执行各自的功能。
从如上的程序运行来看,当程序fork以后,自此之后的程序各自运行各自的程序,而不会打印两次before的内容注意,至于是两个进程谁先运行时取决于谁先拿到时间片谁先运行
写时拷贝技术:
通常情况下父子进程共享代码,当数据不发生改变时,父子数据也是共享的,但是当有父进程或者子进程修改了数据,这个时候,父子进程就会分出两个数据块,用来拷贝修改的数据,这样就大大的提高了创建进程的效率,使得fork成为最常用的创建进程的方式。
fork常规的用法:
1、一个父进程希望复制自己,使得父子进程同时执行不同的代码段。
2、一个进程要执行不同的程序。
fork常见的创建进程失败的原因:
系统中的进程过多,导致用户的进程数超出了限制
2、进程退出
为什么要退出一个进程
1、进程异常终止
2、进程运行完毕
3、释放资源
进程的退出方式:
1、main函数中的return
2、库函数接口exit()
3、系统调用接口_exit()
4、(异常退出)ctrl+c
exit()函数:
#include <unistd.h>
void exit(int status);
退出前刷新缓冲区
可以在任意地方退出进程
_exit()函数:
#include <unistd.h>
void _exit(int status);
不会刷新缓冲区
可以在任意位置退出进程
main函数中return
退出前会刷新缓冲区
只有在main函数中return才会退出一个进程
注:main函数中return的那个值会作为exit中的参数status。
3、进程等待
若没有进程等待,子进程先于父进程退出,就可能出现僵尸进程,会造成内存泄漏。
父进程可以采用进程等待的方式等待子进程退出,并获取子进程的退出信息,并回收子进程资源
wait()函数:
#include <sys/wait.h>
pid_t wait(int* status);
参数:进程的退出状态,不关心可置为NULL
返回值:成功返回pid 失败返回-1
waitpid()函数:
#include <sys/wait.h>
waitpid(pid_t pid, int* status, int options);
参数:
参数 描述 pid_t pid = -1 代表等待任意一个进程 status WIFEXITED(status)若为正常终止的状态,则为真(查看进程是否正常退出)WEXITSTATUS(status)若WIFEXITED(status)非零,提取进程的退出码(查看进程的退出码) options WNOHANG若pid指定的进程没有结束,则waitpid函数返回0,正常结束,返回进程的pid 返回值:正常退出,返回pid,进程没有结束返回0
注意:
若是子进程已经退出调用wait/waitpid时,wait/waitpid立即返回,并且释放资源获取子进程的信息
若是在任意时刻调用,子进程可能阻塞
若是不存在子进程,则立即报错返回
获取子进程的status
wait和waitpid会将子进程的status填充进去
如果传递NULL代表不关心子进程的status
status中低16位中的高八位表示进程的退出状态
status中低16位中的低八位中第八位是core dump标志,剩下的低七位表示进程的终止信号,即被信号杀死的进程的信号。
进程的阻塞等待实现:
进程的非阻塞等待实现:
4、程序替换
用fork创建进程后,子进程执行的是和父进程相同的程序,子进程常需要调用exec函数族中的函数来执行另外一个程序,当进程调用exec函数族中的函数时,该进程的用户空间代码和数据完全被新程序替换。但是进程的ID不会改变,也就是说,程序替换替换的是进程PCB映射在物理内存上的数据段和代码段
替换函数:
#include <unistd.h>
int execl(const char* path, const char* arg, …);
int execlp(const char* file, const char* arg, …);
int execle(const char* path, const char* arg, …, char* const envp[]);
int execv(const char* path, char* const arg[]);
int execvp(const char* file, char* const arg[]);
int execve(const char* path, char* const arg[], char* const envp[]);
这些函数调用成功则加载新的程序,不再返回
出错返回-1
exec函数只有出错返回没有成功返回值
字母 | 描述 |
---|---|
l(list) | 表示参数采用列表的形式 |
v(vector) | 表示参数采用数组的形式 |
p(path) | 表示自动搜索环境变量path |
e(env) | 表示自己指定环境变量 |
区别如下:
函数名称 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 否 | 是 |
execlp | 列表 | 是 | 是 |
execlv | 列表 | 否 | 否 |
execv | 数组 | 否 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 否 | 否 |
只有execve是系统调用接口,其他的都是库函数
总结:
一个程序可以通过调用函数来完成特定的功能,这是程序内部进行功能封装的方式
Linux系统鼓励将此方式运用到进程之间,就有了fork exec进行程序替换,wait/waitpid来进行程序等待获取进程退出的status,最后通过exit/_exit/main函数中的return返回。以此来达到进程之间的替换。