进入了linux进程控制阶段的学习,这一阶段概念比较多,就算学过一遍不可能全部记住,所以争取第一遍学习的时候理解好,解释记录清楚,方便后面快速复习。
目录
一、进程控制
1.创建
fork
写时拷贝技术
vfork
2.终止
1.在main函数中 return
2.库函数:void exit(int retval)
3.系统调用接口:void_exit(int retval);
进程退出返回值的意义
二、进程等待
三、程序替换
四、操作接口
练习:minishell
通过复制夫进程创建一个新的子进程:
pid_t fork(void)
返回值:对于父进程返回新创建的子进程的pid ; 对于子进程返回0;出错创建失败了返回 -1;
通过返回值进行代码分流,让父子进程进入不同的 if 语句分支进行运行
上图中物理内存左侧为父进程,右侧为子进程,mm_struct为虚拟地址,子进程复制了大部分信息包括pcb,虚拟地址等
子进程复制了父进程中的大部分信息,因此子进程有自己的变量,但是自己的变量经过页表的映射后与父进程访问的是同一块物理内存,当这块内存空间中的数据即将要修改,则给子进程重新开辟空间,并拷贝数据过去
作用:主要是提升了子进程创建效率,避免不必要的内存消耗
malloc动态申请一块空间--其实只是先分配了一个虚拟地址(物理内存并没有被消耗),当第一次要修改的空间数据的时候才会被分配
pid_t vfork(void)
创建一个子进程(这个函数在fork实现了写时拷贝技术后,用的很少), 创建一个子进程出来,父子进程共同用一个虚拟地址空间
create a child process and block parent
创建一个子进程并阻塞父进程直到自己成exit退出,或程序替换置=之后,父进程再开始运行
fork创建子进程之后,父子进程均等待系统调度运行
vfork创建子进程,一定是子进程先运行,只有子进程退出或者程序替换之后父进程才会继续运行
父子进程公用一个虚拟地址空间,意味着用的是同一个栈
导致:
1.同时运行导致栈混乱
2.因此先阻塞父进程,直到子进程退出(所有函数出栈)或者程序替换(意味着重新开辟了自己的地址空间,有了自己的栈),父进程才会退出
所以不推荐使用 vfork
进程的退出终止
return 是终止一个函数,返回一个数据,并且仅在 main函数中使用是退出程序运行,其他函数不是;
要知道:main函数是程序的入口函数,入口函数一旦退出,程序运行就会终止
在任意位置调用都可以退出程序运行
系统调用接口是操作系统向上层提供的用于访问内核的接口,功能相对都比较单一
针对典型场景,对系统调用接口进行封装,封装出了适用于典型场景库函数
在任意位置调用,都可以退出程序运行
exit 和 _exit 的区别在于退出程序运行前,是否会将缓冲区中的数据进行刷新写入文件中。
_exit退出时直接释放资源,不会刷新缓冲区
退出程序有多种方式,在合适的场景选择合适的方式进行即可。
return 以及 exit 给与的数据其实就是进程的退出码
作用:
—个程序运行起来肯定是为了完成一个任务,但是这个任务完成的怎么样外界如何得知呢?
因此就必须有这个进程的退出码,来表示当前进程任务处理的结果
父进程在创建子进程后,等待子进程退出,获取子进程的退出码,释放子进程的资源,避免出现僵尸进程。
阻塞操作流程简单,但是对资源利用率低
非阻塞操作稍微复杂,但是对资源利用率高
pcb是描述程序的运行过程
为什么要创建子进程?--创建一个子进程是为了完成一段任务
1.做与父进程相同的事情---压力分摊
2.完成另一个任务(执行另一段代码)
但是如果把另一个任务的代码跟当前的代码合到一起,会导致程序特别庞大,而且模块化不太好
办法:一个任务写一个程序,让子进程这个pcb管理调度这新的程序的运行(程序替换)
程序替换:替换掉一个pcb所描述的要调度管理的程序,替换成另一个程序
简单理解:将一个新程序加载到内存中,然后修改当前pcb进程的页表映射信息,初始化地址空间,让其调度管理新程序
int main()
{
printf("原本运行的程序起始!\n");
execlp("ls","ls","-1",NULL);
printf("原本运行的程序结束!\n");
return 0;
}
程序替换之后,相当于对pcb来说,调度一个新的程序(不关心以前的程序被释放),新程序运行完毕,就会退出进程
int execve(char *path,char *argv[],char *env[]);
实现一个minishell,理解shell的运行机制,熟悉进程相关接口的使用
shell:一个软件,命令行解释器,捕捉用户的输入,了解用户想做什么,然后执行对应的shell命令,起到用户与内核之间进行沟通的桥梁作用
功能:
1.捕捉用户输入 【 ls -l -a 】
2.解析输入,得到命令名称,各个参数[ls] [-l] [-a]
3.创建子进程
4.对子进程进行程序替换,将进程要调度的程序换成要执行的shell指令程序
5.父进程等待子进程退出(等待指令执行完毕(为了避免僵尸进程),等待捕捉下一个输入)
因此思想:父进程不断捕捉用户输入,解析数据,解析完毕后,将任务交给子进程完成
#include
#include
#include
#include
int main()
{
while(1) {
printf("【user@host ~】$ ");
fflush(stdout);//刷新标准输出缓冲区
char cmd[1024] = {0};
gets(cmd);
printf("[%s]\n", cmd);
//[ ls -a -l ]
int argc = 0;
char *argv[32] = {NULL};//定义一个字符指针数组-指向各个参数
char *ptr = cmd;
argv[argc] = strtok(cmd, " ");
printf("[%s]\n", argv[argc++]);
while((argv[argc] = strtok(NULL, " "))!= NULL) {
printf("[%s]\n", argv[argc]);
argc++;
}
//xec(path, argv);
}
return 0;
}