目录
1.进程创建
2. 进程终止
2.1 进程退出的场景
2.2 进程常见退出方法
2.3 return返回终止
2.4 exit()和_exit()
3. 进程等待
3.1 进程等待的原因
3.2 wait编辑
3.3 waitpid
3.4 status
4. 进程替换
4.1 替换原理
4.2 exec函数系列
1.进程创建
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
返回值:自进程中返回0,父进程返回子进程id,出错返回-1
对于返回值的理解:上层概念上的理解
fork在创建子进程时,OS会做的:
- 分配新的内存块和内核数据结构(task_struct)给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
写时拷贝 :通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
fork函数中return id就是写时拷贝
2. 进程终止
2.1 进程退出的场景
- 代码执行完毕,结果正确
- 代码执行完毕,结果错误
- 代码异常终止
2.2 进程常见退出方法
正常终止:
- main函数返回 return
- 调用exit()
- _exit()
异常终止:
- ctrl + c 信号终止
- kill命令 信号终止
2.3 return返回终止
return n 返回值n为0代表结果正确返回,n非0代表结果不正确,在main函数的return才具有这样的意义,函数的返回值没有
2.4 exit()和_exit()
exit是c库提供的函数,_exit是操作系统提供的函数接口
exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:
- 执行用户通过 atexit或on_exit定义的清理函数。
- 关闭所有打开的流,所有的缓存数据均被写入
- 调用_exit
3. 进程等待
3.1 进程等待的原因
子进程退出,如果父进程没有作为,就会导致子进程变成僵尸进程,进而导致内存泄漏
父进程派给子进程的任务,子进程完成的怎么样了,父进程需要获取
3.2 wait
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status)
等待任意一个子进程
返回值:
成功返回被等待子进程的pid,失败返回-1
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
3.3 waitpid
pid_t waitpid(pid_t pid, int* status, int options)
wait函数的功能完善版
返回值:
正常返回等待子进程的pid
调用出错返回-1
options 设置为WNOHANG时 当调用waitpid时发现没有退出的子进程可以收集返回0
参数:
当pid设置为-1时,标识等待任意一个子进程,等同于wait
当pid设置为指定的pid,标识等待这个pid的子进程
status和上面一样下面会介绍
options,为0时标识阻塞等待,父进程会被加载到阻塞队列里,wait默认也是阻塞等待
options,为WNOHANG时标识非阻塞等待,若子进程没有结束,返回0,不予以等待 ,WNOHANG,是宏定义的值为1
3.4 status
status是以位图的形式存储信息
- wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>int main()
{pid_t id = fork();if(id < 0){perror("fork");exit(1);}else if (id == 0){//子进程int cnt = 3;while(cnt){printf("我是子进程pid: %d ppid: %d\n",getpid(),getppid());sleep(1);}exit(11);}else{//父进程sleep(5);int status = 0;pid_t ret = waitpid(id, &status, 0);printf("返回值:%d 子进程退出码: %d 终止信号:%d\n",ret, (status >> 8) & 0xFF, status & 0x7F);sleep(2);
// while(1)
// {
// printf("我是父进程pid: %d ppid: %d\n",getpid(),getppid());
// sleep(1);
// }}return 0;
}
获取退出状态(status >> 8)& 0xFF
获取终止信息 status & 0x7F
也可以使用宏函数来完成获取status中的信息:
WIFEXITED(status)非0表示进程正常结束
WIFSIGNALED(status)非0表示进程信号终止
WEXITSTATUS(status)获取进程退出码
WTERMSIG(status)获取终止信号
进程的非阻塞等待,以及宏函数获取status
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>
#include<stdlib.h>
#include<iostream>
#include<vector>using namespace std;
typedef void (*handler_t)(); void func1()
{printf("任务1\n");
}void func2()
{printf("任务2\n");
}std::vector<handler_t> handlers;
//加载函数
void Load()
{handlers.push_back(func1);handlers.push_back(func2);
}int main()
{pid_t id = fork();if(id < 0){perror("fork");exit(1);}else if(id == 0){//子进程int cnt = 3;while(cnt--){printf("子进程 pid: %d ppid: %d\n",getpid(),getppid());sleep(1);}exit(11);}else{//父进程int status = 0;int quit = 0;while(!quit){pid_t ret = waitpid(id, &status, WNOHANG);if(ret < 0){perror("waitpid");exit(1);}else if(ret == 0){//子进程没有退出if(handlers.empty())Load();for(auto iter:handlers){iter();}sleep(1);}else{if(WIFEXITED(status)){printf("退出码: %d\n",WEXITSTATUS(status));}else if(WIFSIGNALED(status)){printf("退出信号: %d\n",WTERMSIG(status));}quit = 1;}}//printf("父进程 ret: %d 退出码: %d 退出信号: %d\n", ret, (status >> 8)&0xFF, status&0x7F);}return 0;
}
4. 进程替换
4.1 替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
4.2 exec函数系列
- 这些函数调用成功就会加载新的程序序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值。
exec命名的规律:
- l(list): 表示参数采用列表的形式,可变参数列表
- v(vector): 表示参数采用数组的形式
- p(path): 表示路径通过环境变量PATH获取
- e(environ): 表示自己维护环境变量
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve
实现shell
//整体结构:创建子进程,由子进程获取指令,父进程判断完成的怎么样
//1.打印标识开头
//2.获取指令字符串
//3.分析字符串提取指令到grev[]
//4.部分指令的特殊处理,例如cd
//5.替换进程execvpe
//
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>#define SIZE 1024
#define NUM 32char str[SIZE];
char* _grev[NUM];
char _env[NUM][NUM];int main()
{int num_env = 0;while(1){//1.printf("[root$loadhost myshell]# ");fflush(stdout);//2.memset(str,SIZE,'\0');fgets(str, SIZE, stdin);int sz = strlen(str);str[sz - 1] = '\0';//3._grev[0] = strtok(str, " ");int index = 1;//4.if(strcmp(_grev[0],"ls") == 0){_grev[index++] = (char*)"--color=auto";}if(strcmp(_grev[0], "ll") == 0){_grev[0] = (char*)"ls";_grev[index++] = (char*)"--color=auto";_grev[index++] = (char*)"-l";}while(_grev[index++] = strtok(NULL, " "));if(strcmp(_grev[0], "cd") == 0){if(_grev[1]) chdir(_grev[1]);continue;}if(strcmp(_grev[0], "export") == 0 && _grev[1]){memcpy(_env[num_env],_grev[1],strlen(_grev[1])+1);putenv(_env[num_env]);num_env++;continue;}pid_t id = fork();if(id < 0){perror("fork");exit(1);}else if(id == 0){//child//5.execvp(_grev[0], _grev);exit(2);}else {//fatherint status = 0;pid_t ret = waitpid(id, &status, 0);if(ret < 0){printf("等待子进程失败\n");exit(2);}else{if(WIFEXITED(status)){printf("子进程退出码:%d\n",WEXITSTATUS(status));}else if(WIFSIGNALED(status)){printf("子进程终止信号:%d\n",WTERMSIG(status));}}}}return 0;
}
完