目录
1.进程创建
1.1fork
1.1.1fork函数
linux下创建进程的方式:
- ./可执行程序,
- 调用fork,创建成功子进程给父进程返回子进程id,给子进程返回0,当然还有创建失败的情况
1.1.2fork写实拷贝
1.1.3fork常规用法
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
1.1.4fork调用失败的原因
系统中有太多的进程
实际用户的进程数超过了限制
创建进程是有成本的
1.2进程终止
2. 进程等待
2.1相关概念
2.2进程等待方法
代码一:wait小测试
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child[%d] is running :cnt is : %d\n",id,cnt);
cnt--;
sleep(1);
}
exit(0);
}
pid_t ret = wait(NULL);
if(ret>0)
printf("father wait:%d, success\n",ret);
else
{
printf("father wait failed!\n");
}
return 0;
}
代码二:wait执行过程中进程状态深入体现
#include<stdio.h> #include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child[%d] is running :cnt is : %d\n",id,cnt);
cnt--;
sleep(1);
}
exit(0);
}
sleep(10);
printf("father wait begin!\n");
pid_t ret = wait(NULL);
if(ret>0)
printf("father wait:%d, success\n",ret);
else
printf("father wait failed!\n"); sleep(10); return 0;
}
代码和下图分析:
子进程执行五秒,
父进程先等待10秒,前五秒子进程正常运行,后五秒子进程变成了僵尸状态,
father开始等待
僵尸状态此时没了
父进程在等待10秒 (此时的进程两个变一个,此时只剩下父进程一个)
即:wait是可以回收僵尸进程的
代码三:waitpid
代码和代码二类似
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("child[%d] is running :cnt is :%d\n",getpid(),cnt);
cnt--;
sleep(1);
}
exit(0);
}
sleep(10);
printf("father wait begin!\n");
pid_t ret = waitpid(id,NULL,0);
if(ret>0)
printf("father wait:%d,success\n",ret);
else
printf("father wait failed!\n");
sleep(10);
return 0;
}
返回值:
如果成功,就会返回你等待的那个子进程,那个子进程退出了,我们返回的就是那个子进程的id
如果失败了,就是-1
子进程是11314等待的也是,,所以没毛病
2.3进程等待中"waitpid"处status参数
pid_t ret = waitpid(id,NULL,0)//等待指定一个进程
pid_t ret = waitpid(-1,NULL,0)//等待任意一个子进程,等价于wait
pid_ t waitpid(pid_t pid, int *status, int options);//第二个参数是输出性参数
将我们的测试代码中一部分不需要的删除
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 3;
while(cnt)
{
printf("child[%d] is running :cnt is :%d\n",getpid(),cnt);
cnt--;
sleep(1);
}
exit(0); //第一次这里是0,第二次随便传入一个值
}
printf("father wait begin!\n");
int status = 0; //我们探究的就是这个值
pid_t ret = waitpid(id,&status,0);
if(ret>0)
printf("father wait:%d,success,status:%d\n",ret,status);
else
printf("father wait failed!\n");
return 0;
}
我们可以推出:
父进程拿到什么status结果,一定和子进程如何退出强相关!!!
子进程退出无非三种结果, 则我们可以通过status反馈出子进程退出的情况
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 3;
while(cnt)
{
printf("child[%d] is running :cnt is :%d\n",getpid(),cnt);
cnt--;
sleep(1);
}
exit(11);
}
printf("father wait begin!\n");
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret>0)
printf("father wait:%d,success,status exit code :%d,status exit signal:%d\n",ret,(status>>8)&0xFF,status&0x7F);
else
printf("father wait failed!\n");
return 0;
}
~
上图表示:我们的代码是正常退出的
11是我们设置的,0是代表没有接受到异常终止信号
关于异常终止信号我们可以:
kill -2 子进程
这行指令的意思是,我们让子进程直接退出,退出的原因是2
那么我们执行上面的代码时(在子进程进行的时候我们kill)
最后显示的异常终止信号就是2
当我们写的代码出现问题时:1/0
这个明显是错误的
那么我们最终显示的就是浮点数错误:8号信号SIGFPE
2.4重新解读单个进程的pid和ppid
可以得出:
bash是命令行启动的所有进程的父进程!
bash一定是通过wait方式得到子进程的退出结果(退出码)
所以我们能看到echo $?能够查到子进程的退出码
2.5进程等待中waitpid的第三个参数options
阻塞本质:进程的PCB被放入了等待队列,并将进程的状态改为S状态
返回的本质:进程的PCB等待队列拿到运行队列,从而被CPU调度
看到某些应用或者OS本身,卡住了长时间不动,应用或者程序hang住了
WNOHANG:非阻塞
- 子进程根本就没有退出
- 子进程退出,waitpid(调用成功or失败)
基于非阻塞的轮询方案
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 3;
while(cnt--)
{
printf("child[%d] is running ;cnt is:%d\n",getpid(),cnt);
sleep(1);
}
exit(1);
}
int status = 0;
while(1){
pid_t ret = waitpid(id,&status,WNOHANG);
if(ret == 0)
{
//子进程没有退出,但是waitpid等待是成功的,需要父进程重复进行等待
printf("do father thing!\n");
}
else if(ret>0)
{
//子进程推出了,waitpid也成功了,获取到了对应的结果
printf("father wait:%d,success,status exit code:%d ,status exit signal:%d\n",ret,(status>>8)&0xFF,status&0x7F);
}
else{
//等待失败,
perror("waitpid");
break;
}
3. 进程程序替换, 微型shell,重新认识shell运行原理
3.1简单了解程序替换
进程不变,仅仅替换当前进程的代码和数据的技术,叫做进程的程序替换
程序本身就是一个文件!!!
文件=程序代码+程序数据
问题:程序在进行程序替换的时候有没有创建新的进程?
没有,只是把我们老程序的壳子不变,新程序的文件替换进来
#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[]);
int execve(const char *path, char *const argv[], char *const envp[]);
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
printf("i am a process! pid: %d\n",getpid());
execl("/usr/bin/ls","ls","-a","-l",NULL);
//ececl执行程序替换,
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
return 0;
}
问题:
程序替换的本质是不是就是吧特定的进程代码+数据,加载到特定进程的上下文中?
我们知道C/C++程序要运行,必须要先加载到内存中
如何加载?(就是讲程序和数据拷贝到内存中)加载器!
加载器-> exec*程序替换函数!!!
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("i am a process! pid: %d\n",getpid());
sleep(3);
execl("/usr/bin/ls","ls","-a","-l",NULL);
//ececl执行程序替换,
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
printf("Hello sakeww!!!!!\n");
}
while(1)
{
printf("i am a father\n");
sleep(1);
}
waitpid(id,NULL,0);
printf("wait success!\n");
return 0;
}
问题:
为什么父进程没有受影响呢?
进程具有独立性!
但是我们又知道:父子代码是共享的吗?
进程程序替换会更改代码区的代码,也要发生写时拷贝!
问题:
只要进程的程序替换成功,就不会执行后续代码,意味着exec函数,成功的时候,不需要返回值检测?
换一种问法:
只要exec返回了,就一定时因为调用失败了?
程序替换失败时:
3.2基本程序替换函数的使用
#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[]);
int execve(const char *path, char *const argv[], char *const envp[]);
即:ececl("/usr/bin/ls","ls",-a","-l",NULL);
在vim中查询“execl”相关信息
3.4样例测试
ececl("/usr/bin/ls","ls",-a","-l",NULL)
内容解释:
./myexec
变成一个进程,在内部就会执行被替换的代码
printf("执行结束!\n);
没有被执行,是因为:
我们进行了程序替换,就叫做替换所有的代码
只要程序执行exec返回了必定出错了!
帮助理解:
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[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
int execv(const char *path, char *const argv[]);
int execv(const char *path, char *const argv[]);
int execl(const char *path, const char *arg, ...);
这两个无任何效果上的区别,
第二个是可变参数列表
第一个是数组,里面的元素可以由自己定
argv类似我们的main函数的参数列表
int execlp(const char *file, const char *arg, ...);
因为l:所以传参都是列表形式
const char *file,只要告诉这个程序的名字即可,不用告诉这个程序在哪里
可以在环境变量PATH帮你搜索这个程序
int execle(const char *path, const char *arg, ...,char *const envp[]);
在c中执行py程序
3.5接口总结
3.6模拟实现一个简单shell
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<string.h>
#define NUM 128
#define CMD_NUM 64
int main()
{
char command[NUM];
for(;;){
char *argv[CMD_NUM] = {NULL};
//1.打印提示符
command[0] = 0;//用这种方式,可以做到O(1)时间复杂度,清空字符串
printf("[who@myhostname mydir]# ");
fflush(stdout);
//2.获取命令字符串
fgets(command,NUM,stdin);
command[strlen(command)-1] = '\0';//我们的输入中,最后会以回车结尾,导致换行两次
//3.解析命令字符串,解析成char *argc[];
//strtok();
const char *sep = " ";
argv[0] = strtok(command,sep);
int i = 1;
while(argv[i] = strtok(NULL,sep))
{
i++;
}
//4.检测命令是否是需要shell本身执行的,内建命令
if(strcmp(argv[0],"cd") == 0)
{
if(argv[1] != NULL)
chdir(argv[1]);
continue;
}
//5.执行第三方命令
if(fork() == 0)
{
execvp(argv[0],argv);
exit(1);//上面一行执行成功就不会执行本行代码
}
waitpid(-1,NULL,0);
}
}