文章目录
进程创建
pid_t fork(void)
通过复制父进程创建子进程,父子进程代码共享,资源独有
fork()的返回值:(用于区分父子进程)
父进程,返回子进程的pid,大于0
子进程,返回0
出错时返回-1
pid_t vfork(void)—这种方法用的很少
–创建一个子进程并阻塞父进程,父子进程共用虚拟地址空间
–如果父子进程同时运行会造成栈混乱,所以vfork()阻塞了父进程,让子进程先运行,直到子进程exit退出或者程序替换后,父进程才能继续运行
写时拷贝技术
通过复制父进程创建子进程,所以父子进程一开始映射的是同一块物理内存地址,但是当数据需要发生改变时,则重新为子进程开辟新的空间,将数据拷贝过去
优点:创建子进程的效率高
进程退出
1、main函数中的return
只有main函数中的return可以退出进程,其他地方不可以
2、库函数 void exit(int status)
可以在任意位置调用退出进程,退出前刷新缓冲区
3、系统调用接口 _void exit(int status)
可以在任意位置调用退出进程,不刷新缓冲区
status可以视为进程的退出返回值,即程序的退出原因,保存在pcb中
使用ehco &?命令 可以获取 上一个已退出进程的status值
查看上一次系统调用使用错误的原因
1、void perror(const char* s);
打印一个系统错误信息
const char* s为用户自定义的备注信息,例如:
表示程序运行成功,未出现错误。
2、char *strerror(int errnum);
errnum–全局变量,程序错误序号
通过程序错误序号获取错误信息
进程等待
父进程等待子进程推出,获取其退出返回值,释放子进程的资源
目的:避免产生僵尸进程
头文件:#include <sys/wait.h>
1、 pid_t wait(int *status);
阻塞等待任意一个子进程退出,通过status获取退出返回值,释放资源
**返回值:**若成功,返回退出的子进程的pid;若出错,返回-1
2、pid_t waitpid(pid_t pid, int *status, int options);
可以等待任意一个子进程退出,也可以等待指定的子进程退出
pid_t pid:等于-1,表示等待任意子进程退出;大于0,表示等待指定子进程退出
可以是阻塞等待子进程退出,也可以是非阻塞等待子进程退出
int options:设置为0,执行阻塞等待;设置为WNOHANG,则执行非阻塞等待
返回值:若成功,返回子进程pid;
若没有子进程退出,返回0(非阻塞状态下,阻塞状态下会一直等待);
出现错误,返回-1
注意:如果有已经退出的子进程,wait/waitpid会直接处理,无需等待
阻塞和非阻塞
阻塞:为了完成一个功能,发起一个调用,若功能完成条件不具备,就一直等待
非阻塞:为了完成一个功能,发起一个调用,若功能完成条件不具备,立即报错返回
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int pid=fork();
if(pid<0)
{
//出错
perror("fork error");
return -1;
}
else if(pid==0)
{ //子进程
sleep(5);//休眠5秒
return 99;//main中的return--退出子进程,退出返回值为99
}
else
{
//父进程
while(1)sleep(1);//保持休眠
}
return 0;
}
子进程休眠5秒后退出,进入僵尸态,会发生内存泄漏
下面修改代码,加入内存等待操作
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
int pid=fork();
if(pid<0)
{
//出错
perror("fork error");
return -1;
}
else if(pid==0)
{
//子进程
sleep(5);
return 99;//main中的return--退出子进程,退出返回值为99
}
//int ret=waitpid(-1,NULL,0); //与21行等价,用哪个都行
int ret=wait(NULL);//等待任意子进程退出
printf("progress:%d exit\n",ret);
//父进程
while(1)sleep(1);//死循环
return 0;
}
打印信息:pid=6035的子进程退出
结果显示子进程直接退出,已经不存在了
再来看waitpid非阻塞状态的代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
int pid=fork();
if(pid<0)
{
//出错
perror("fork error");
return -1;
}
else if(pid==0)
{
//子进程
sleep(5);
return 99;//main中的return--退出子进程,退出返回值为99
}
// int ret=wait(NULL);
// printf("progress:%d exit\n",ret);
int ret;
while((ret=waitpid(-1,NULL,WNOHANG))==0)
{
//非阻塞状态下,没有子进程退出,返回0
printf("当前子进程还没有退出\n");
sleep(1);
}
if(ret<0)
{
perror("waitpid error:");
}
printf("progress:%d exit\n",ret);
//父进程
while(1)
{
sleep(1);//死循环
}
return 0;
}
进程替换
替换一个进程正在调度运行的程序
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
6种进程替换函数
#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 argv[]);
int execvp(const char *file, char *const argv[]);
函数解释
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。
命名理解
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
函数名 | 参数格式 | 带路径? | 使用当前环境变量? |
---|---|---|---|
execl | 列表 | 不是 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 不是 | 不是,必须自己组装环境变量 |
execv | 数组 | 不是 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 不是 | 不是,必须自己组装环境变量 |