1.进程创建
1.1 fork函数
fork函数可以在已存在的进程中创建一个新的进程,新进程为子进程,原进程为父进程
fork之后分子两个执行流分别执行,进程具有独立性,代码和数据必须是独立的,由于代码只能读取,所以父子进程的代码是共享的(全部共享,只不过子进程是从fork后的代码开始执行的),而子进程的数据是采用写时拷贝的策略。
1.2写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
父进程的数据子进程不一定全部写入,写时拷贝可以减少空间的浪费,而其写时拷贝还采用延迟拷贝的策略,当需要的时候在分配空间,变相挺高了内存的使用率
1.3 fork调用失败的原因
fork的调度是有可能失败的,当系统中有太多的进程或者实际用户的进程数超过了限制时都有可能失败。
2. 进程终止
进程退出场景
1、代码运行完毕,结果正确
2、代码运行完毕,结果不正确
3、代码异常终止
我们在C/C++中main函数中的return就是将进程结束的结果返回给父进程,0表示成功
可以用$?查看最近一次执行完毕时,对应的进程退出码
2.1进程终止的常见方法
1、在main函数中return
2、在代码任意位置调用exit()/_exit()
exit()终止进程,刷新缓存区
_exit()终止进程,不刷新缓冲区
3.进程等待
子进程退出,不会进入死亡状态,而是先进入僵尸状态,将退出信息给父进程,如果父进程不管不顾,就可能造成‘僵尸进程’的问题,因此父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
3.1进程等待的方法
1、waitpid
也可以用系统提供的宏来查看status信息
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
waitpid的最后一个参数options如果的0代表阻塞等待,如果是WNOHANG代表非阻塞等待
如果等待的过程是阻塞等待,那么如果等待的子进程没有退出,父进程就会一直在那里等,直到子进程退出,而如果是非阻塞等待,那么如果等待的子进程没有退出,父进程可以去执行其他代码
如果将等待方式换成非阻塞等待,那么在子进程没结束的时候父进程就会接着执行自己的代码
4.进程程序替换
4.1替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
4.2进程替换函数结构
1.execl(list)
int execl(const char *path, const char *arg, ...);
path:接收新程序的路径
arg:接收程序执行的选项(比如:ls -l -a arg中就填 “ls” "-l" "-a" ) 以null作为结束标识符
...:表示可变参数
如果程序替换失败返回-1
2.execv(vector)
int execv(const char *path, char *const argv[]);
path:接收新程序的路径
argv[]:是一个字符指针数组,也就将所有的选项放在一个数组中
3.execlp(PATH)
int execlp(const char *file, const char *arg, ...);
file:执行文件名(这个函数自带了搜索路径PATH,所以如果我需要执行的程序在这个路径上就可以直接写文件名,而不用写路径)
arg:与execl相同
3.execvp
int execvp(const char *file, char *const argv[]);
与execlp类似,不过选项是放在一个指针数组里面
4.execle
int execle(const char *path, const char *arg,..., char * const envp[]);
在讲这个接口前先让我们开一下这个问题,如何从C语言程序中调用C++程序?
从新编辑一下makefile,让它可以同时生成两个可执行文件
execle接口可以在调用子进程的时候给子进程一个环境变量
如果直接调用mycmd程序会崩溃,因为环境变量中没有MYPATH
我们在myexec中调用mycmd,并传一个环境变量给它,就可以正常运行
5.execvpe、execve
int execvpe(const char *file, char *const argv[], char *const envp[]);
int execve(const char *filename, char *const argv[], char *const envp[]);
经过前面四个的讲解,不难发现其中的规律
在手册中可以看到execve在单独一个手册,因为execve才是真正的系统接口,而其他是对execve的封装,为了去适配不同的情况
4.3 实现一个简易的shell
shell其实就是在shell程序中去执行其他的程序,有了上面的基础后,我们就可以实现一个简易的shell
先将makefiel中mycmd替换成myshell
如果把可执行程序变成绿色?
系统中用的ls其实是'ls --color=auto'的别名
所以当指令输入的是ls的时候,自动加上--color=auto就能和系统命令一样带颜色
我们的shell还有一个问题,pwd指令的固定的
这是因为exec执行cd,只能让子进程进行路径切换,而子进程一运行完就结束了,所以我们每次pwd命令都是新的子进程,我们需要让父进程shell自己执行,shell自己执行的命令我们称为内建命令
同样的内建命令还是有export、echo等
环境变量会被子进程继承下去,所以他会有全局属性,进行进程替换的时候环境变量不会被替换