(Linux) 进程控制

一、进程创建

1.fork

①什么是fork?

fork是Linux中非常重要的函数,它是从已经存在的进程(父进程)中创建的一个新的进程(子进程)。

②返回值

子进程返回0。
父进程返回子进程pid。
出错返回-1。
通过fork返回值,决定执行父进程or子进程代码 if-else。

③关系

子进程会继承父进程的pcb。
子进程会拷贝父进程虚拟地址空间,拷贝一份页表。
父子进程共用一份代码(修改时,写时拷贝父进程物理地址空间),拥有两份数据(代码段,数据段相同)。

④调用fork函数

调用fork函数之后,父子进程各自拥有一个独立的执行流,父子进程均会从fork之后的指令开始执行。
内核会分配新的内存块和内存数据结构给子进程,并将父进程部分数据结构内容拷贝给子进程。

⑤先后顺序

fork之后,父子进程两个执行流分别执行。谁先执行不确定,由调度器决定。

⑥用法

父子进程需要执行不同代码段。eg:父进程等待请求,子进程处理请求。
父子进程执行不同程序。eg:子进程从fork返回后,调用exec函数。

⑦失败原因

内存不够。
进程太多。


2.vfork
vfork与fork基本相同
①区别

父子进程共用同一个虚拟地址空间,共用同一份页表。
保证子进程先执行,在子进程调用exec或_exit之后父进程才开始执行。(不能同时执行,父进程先挂起等待)


二、进程终止

1.退出方式

①代码运行完毕,结果正确,返回0
②代码运行完毕,结果错误,返回非0
③代码异常终止,进程收到信号

(PS:查看进程退出码,用 echo $? 指令)

(PPS:$,shell取一个变量的数值;?,变量中内容为上一个进程的退出码)

2.退出方法

  1. main - return

return是一种常见的退出进程的方式。执行return(n)与exit(n)等价。因为调用main函数时,会将main函数的返回值作为exit()的参数。

  1. crtl + c ,信号终止(异常退出)
  2. exit,库函数,终止进程
#include <unistd.h>
void exit(int status);
//参数:定义了进程的终止状态,父进程通过wait来查看
  1. _exit,系统调用
#include <unistd.h>
void _exit(int status);
//参数:定义了进程的终止状态,父进程通过wait来查看
//虽然status是int,但是只有低8位可以被父进程所用。所以_exit(-1)是,在执行*echo $?*查看到的返回值是255.

注:exit()在执行完以下操作后,还是会调用exit_。

  1. 执行用户通过atexit(用户自定义清理函数,只注册)或on_exit定义的清理函数;
  2. 关闭所有打开的流(文件),所有的缓存数据均被写入;
  3. 调用_exit。

用图片简易实现一下:


三、进程等待

1.背景(为什么需要进程等待)

父进程比子进程后结束,可能会造成Z(僵尸)进程,进程等待就是用来处理僵尸进程

2.进程等待方法——wait(阻塞式等待)

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);
//返回值:成功返回被等待进程的pid,失败返回-1.
//参数:输出型参数,获取子进程退出状态(也可直接置为NULL)。

1.输出型参数(位图:最低一个字节表示是否异常终止;倒数第二个字节表示进程的退出码)(类比:Node**)
2.无差别等待
3.子进程:结束:立即读取返回值;未结束:阻塞式等待

注:wait次数与子进程数目相匹配

wait > 子进程,进入僵尸进程;
wait < 子进程,阻塞式等待。(子进程未结束,影响执行效率)

3.进程等待方法——waitpid(非阻塞式等待)

pid_t waitpid (pid_t pid , int* status , int options);

//返回值:0 , (子进程未结束);
//		-1 , (错误,现在没有完成的子进程);
//		pid , (成功)。
//参数:
pid :
	pid = -1 ; //等待任意一个子进程。与wait等价
	pid > 0 ;  //等待与其进程id与pid相等的子进程
status:(可直接用下面的宏替换)
	WIFEXITED(status);//若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
	WEXITSTATUS(status);//若WIFEXITED非零,提取-子进程退出码。(查看进程退出码)
options:
	WNOHANG;//若pid指定的进程没有结束,则waitpid()函数返回0;不等待。若正常结束,返回子进程pid。

1.与wait区别:

①参数不同
②指定等待某个子进程
③支持非阻塞式等待

注:wait与waitpid的区别与联系

1.如果子进程已经结束,调用wait/waitpid本质没有区别,都是获取子进程pid,并释放资源。
2.任意时刻调用wait/waitpid,子进程如果存在并且正在运行,进程可能阻塞。
3.如果不存在该子进程,立即退出并返回-1。
4.wait和waitpid的status参数,如果不关心子进程退出状态,则直接置为NULL。

注:阻塞式等待与非阻塞式等待

1.阻塞式等待:wait,等待下一个子进程结束;
2.非阻塞式等待:waitpid,如果子进程未结束,waitpid立刻返回0,没有等到;没有子进程,返回-1,说明等待错了。(未找到下一个结束的子进程,则去做自己的事,过一会再来查看,如此往复,知道等到下一个结束的子进程。)
3.非阻塞式等待与阻塞式等待相比较,定制化更强,使用起来更灵活,时间利用率更高,同时代码也更复杂。


四、进程替换

1.原理

用fork创建子进程后执行的是和父进程相同的进程(有可能执行不同的代码分支),子进程通常都要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新进程替代,从新进程的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并没有发生改变

共用同一个PCB,PCB内其他属性并没有发生变化,只是替换了代码段和数据段,并把堆和栈重新开始。

2.替换函数
exec函数:

#include <unistd.h>

int execl (const char* path , const char* arg , ...);
//库函数;参数采用列表格式;不带路径;使用当前环境变量
//(*path): 一种可执行文件目录路径(完整路径),绝对/相对路径 ; (*arg): 第0个参数为可执行文件本身 ;(...): n个参数,main函数的命令行参数传过去

int execlp (const char* file , const char* arg , ...);
//库函数;参数采用列表格式;带路径;使用当前环境变量
//(*file): 只是文件名

int execle (const char* path , const char* arg , ... , char* const envp[]);
//库函数;参数采用列表格式;不带路径;不能使用当前环境变量,必须自己组装环境变量
//(*path): 绝对/相对路径 ; (envp[])环境变量

int execv (const char* path , char* const argv[]);
//库函数;参数采用数组(向量容器)格式;不带路径;使用当前环境变量
//(*file): 只写文件名,自动在path中查找 ;  (argv[]): char* 数组,每一个数组是一个命令行参数,并且以空指针做结束标志 


int execvp (const char* file , char*n const argv[]);
//库函数;参数采用数组(向量容器)格式;带路径;使用当前环境变量
//(*file): 只写文件名,自动在path中查找 ;  (argv[]): char* 数组,每一个数组是一个命令行参数,并且以空指针做结束标志 


int execve (const char* path , char* const argv[] , char* const envp[]);
//系统调用;参数采用数组(向量容器)格式;不带路径;不能使用当前环境变量,必须自己组装环境变量
// (*path): 绝对/相对路径 ; (argv[]): char* 数组,每一个数组是一个命令行参数,并且以空指针做结束标志 ; (envp[])环境变量

注:

const char* p;
char const* p;
//等价,指针指向变量内存不能修改

char* const p;
//指针指向不能修改,值也不能修改

函数小结:

exec函数调用成功(没有返回值),原有代码被替换
调用失败返回-1

命名:

l(list) : 参数使用列表格式
v(vector) : 参数使用数组格式
p(path) : 有p自动搜索环境变量PATH
e(env) :自己维护环境变量

#include <unistd.h> 

//int execl (const char* path , const char* arg , ...);
int main() {
	int ret = execl("./test" , "./test" , NULL);
	printf ("ret = %d\n" , ret);
}
//execl执行成功,执行程序被替换;不再printf ret,栈被清空(main在栈上)
//execl执行失败,替换失败,打印出返回值ret
#include <unistd.h> 

int ret execl ("/bin/ls" , "/bin/ls" , "-l" , "-t" , "-r",NULL);
printf("ret = %d\n" , ret);

//有p,可以自己使用环境变量PATH,不需要写全路径
ret = execlp ("ls" , "ls" , "-l" , "-t" , "-r" ,NULL);
printf("ret = %d\n" , ret);

const char* env[] = {"A = 12345 , NULL"};

//有e, 自己组装环境变量
ret = execle ("ls" , "ls" , "-l" , NULL , env[]);
printf("ret = %d\n" , ret);

ret = execv("/bin/ls" , argv);
printf("ret = %d\n" , ret);

ret = execvp ("ls" , argv);
printf("ret = %d\n" , ret);

ret = execve ("/bin/ls" , argv , env[]);
printf("ret = %d\n" , ret);




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值