文章目录
1 . fork函数
1 .1 写时拷贝
fork为什么会返回两个值呢?
答:这是因为发生了写时拷贝。
调用fork后父子进程的地址空间和页表等是完全相同的,只有在子进程要对变量等进行更改时才会单独为子进程开辟新空间,这种技术叫做写时拷贝。
1 . 2 fork失败的原因
如果调用的进程数目过多,就会导致fork调用失败。
2 . 进程终止
2 .1 进程终止时,os做了什么?
答:释放进程申请的相关内核数据结构和对应的数据和代码。
2.2 进程终止的常见方式
a. 代码跑完, 结果正确
b. 代码跑完,结果不正确
main函数的返回值为什么是0?可以是其他值吗?
答:可以,main函数的返回值返回给上一级进程,用来评判进程执行的结果。返回0,代表程序运行结果正确,非0代表结果错误。返回不同的非0值代表不同的错误原因。同时,返回数字太过于抽象,要设计一套将返回的错误码转换成字符串的方案。
实际上,每个进程都有进程退出码,退出码能通过strerror等函数转化成字符串。
c. 代码没有跑完,程序崩溃
程序如果崩溃,那么退出码就没有了意义。
这个时候os会发送进程信号来杀死进程
小结: 如果程序正常跑完,那么就根据退出码来表示进程退出状态,根据零和非零来表示结果是否正确。 如果进程异常终止,那么就根据发送的信号类型采取不同编号的kill指令杀死进程。
2.3 用代码如何终止一个进程?
- return语句 + 退出码, 只有main函数内部的退出语句才是终止进程。
- exit + 退出码 : exit(退出码)。
- _exit + 退出码 : _exit(退出码)。
- _exit和exit的区别: _exit会直接调用系统接口直接关闭程序,不会管缓冲区啥的问题,但是exit会帮助处理后事。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4
5 int main()
6 {
7 printf("我出来了喔\n");
8 sleep(3);
9 exit(1);
10 _exit(1);
11 return 0;
12 }
// \n刷新缓冲区,如果把\n去掉,且使用_exit的话,这句话就不会打印出来
3 . 进程等待
3 .1 为什么要有进程等待
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息.
3.2 如何进行进程等待
调用系统接口wait,waitpid等待子进程死亡即可回收。wait/waitpid方法通过获取进程结构体PCB中的相关进程退出码或者是进程退出信号返回给父进程。
3.2.1 wait方法
wait + status.
这里的status传null即可。
3.2.3 waitpid方法
waitpid有三个参数,pid, status, options
pid: 如果非0表示要等待的子进程的pid,如果传-1表示等待任意一个子进程。
status:这是一个输出型参数,wait将子进程退出的状态原因存入这个变量中。
要注意的是status是根据二进制位设计的,
status这个数的低16位的高八位代表退出码,低七位表示退出信号,查看退出码可以用下图方式。
同时系统给我们提供了WIFEXITED和WEXITSTATUS两个宏替换,用宏替换也可以查看退出码。
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
具体用法:
if (WIFEXITED(status))
{
printf("退出码: %d\n", WEXITSTATUS(status));
}
else
{
printf("进程退出异常\n");
}
options:options默认为0,表示阻塞式的等待,但是如果想在等待期间做一些其他事情的话,就要更改options参数为WNOHANG(wait no hang),这也是一个宏. 如果使用非阻塞的调用方式,那么需要多次调用访问子进程是否退出。
示例:
int num = 0;
while (!num)
{
int status;
pid_t res = waitpid(-1, &status, WNOHANG);
if (res > 0)
{
//表示等待成功 && 子进程退出
printf("子进程已退出, 退出码: %d\n", WEXITSTATUS(status));
num = 1; //父进程退出循环检测
}
else if (res == 0)
{
//等待成功 && 子进程还未退出
printf("子进程还未死亡\n");
//在这里可以做其他的事
//...
}
else
{
//等待失败
printf("等待失败\n");
num = 1; //等待失败同样退出检测
}
}
4 . 进程替换
通过fork创建出来的子进程和父进程是共享某一段代码的,但是如果子进程如果想要拥有全新的属于自己的一段代码该怎么办呢?这就要用到进程替换。
4.1 进程替换是什么?
进程替换就是通过调用系统接口,从磁盘中加载一段全新的程序来替换子进程。
4.2 进程替换是怎么做的?
通过建立新的页表映射,使得当前进程指向新的代码和数据。
4.3 如何用代码进行进程替换
要调用execl系列系统接口将新的程序加载到内存
以execl函数为例解释:
函数签名如下:
int execl(const char *path, const char *arg, ...)0
这里有“2”个参数,path代表目标程序的路径,arg 和 … 视为一个参数,表示可变参数列表:可以传入多个不定个数参数, 但是最后必须以NULL结尾,表示输入结束。
以ls命令为例这个函数可以这么填
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
以自己写的一个test程序为例
test程序只有一句打印hello world的语句,proc经过下午替换后打印的结果如示
如果替换失败就会返回-1,成功不返回值,因为execl是从自己开始到最后一条语句全部替换,他自己都被替换了,还有啥可返回的。
/
execv: 和 l类型接口不同(cl系列,可以用list来记忆,传的参数是一个一个传。), v类可以用vector来记忆,传参需要用一个数组存储指令。
使用方法:
主要有两大类的差别: v类和l类(v类看作vector,通过数组方式传参;l类看作list,自己一个一个传参), p类和e类(p即PATH,可以不写路径,通过环境变量查找;e即env,可以通过数组将环境变量传递给替换后的进程)。
execlp:execlp(“ls”, argv);
execvp: execvp(“ls”, argv); //argv中存储着要使用的参数
execle: execle(“/usr/bin/ls”, “ls”, “-a”, NULL, env)
execvpe:(“ls”, “ls”, “-a”, NULL, env)//env中存储要传递的环境变量
要说是系统真正提供给我们的调用接口,还得是execve,上面的函数底层调用的全部都是execve接口,是系统做的基本封装。
小技巧
在vim底行模式下 %s/替换目标/替换源/g,可以批量化替换。
5 . 写一个minishell
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define NUM 1024
#define SIZE 32
char login[NUM];
char* work[SIZE];
int main()
{
//获取命令 -- 解析命令 -- 执行特殊命令 -- 执行
//父进程等待子进程
while (1)
{
//1. 获取命令
printf("[zy@VM-12-14-centos----Myshell]$ ");
fflush(stdout);
memset(login, '\0', sizeof login); //全部设为'\0'
if (fgets(login, sizeof login, stdin) == NULL)
{
continue;
}
//除掉最后一个'\n'
login[strlen(login) - 1] = '\0';
//check
//printf("命令:%s\n", login);
//2.解析命令
//strtok
work[0] = strtok(login, " ");
int cnt = 1;
if (strcmp(work[0], "ls") == 0)
{
work[cnt++] = "--color=auto";
}
while (work[cnt++] = strtok(NULL, " "));//一直分割一直传
//check
//for (cnt = 0; work[cnt]; ++cnt)
// printf("第%d个命令: %s\n", cnt + 1, work[cnt]);
//
//3.执行cd命令
if (strcmp(work[0], "cd") == 0)
{
if (work[1] == NULL) continue;
chdir(work[1]);
}
//4. 执行
//创建子进程进行执行,父进程等待
pid_t id = fork();
if (id == 0)
{
//child
//替换进程
execvp(work[0], work);
printf("guess what? can you see me?\n");
}
else if (id < 0)
{
printf("创建失败\n");
continue;
}
//父进程
int status;
pid_t res = waitpid(-1, &status, 0);
if (res > 0) printf("子进程退出,退出码: %d\n", WEXITSTATUS(status));
}
return 0;
}
经测试可以实现基本指令,且带颜色。