<Linux>进程控制

目录

一、进程创建

写实拷贝:

二、进程终止

三、进程等待


一、进程创建

描述一下,fork创建子进程,操作系统都做了什么?

fork后父子进程是全部包括之前的代码都共享;

        

fork创建子进程(内核数据结构(OS创建) + 进程代码和数据(一般从磁盘中来,也就是C/C++程序加载之后的结果));

创建子进程,给子进程分配对应的内核结构,必须是子进程自己独有的,因为进程具有独立性,理论上,子进程也要有自己的代码和数据,可是一般而言,我们没有加载的过程,也就是说,子进程没有自己的代码和数据,所以子进程只能使用父进程的代码和数据那么:

代码:都是不可修改的,只能读取,所以父子共享是没有问题的!

数据:可能被修改的,所以必须分离!

对于数据:

创建子进程时,不需要将不会访问的,或者只会读取的数据都拷贝一份,但是为了保证独立性,还是需要一定数据的拷贝,这里只拷贝将来会被父进程或者子进程写入的数据;但是一般而言,即使是操作系统也无法提前知道那些空间可能会被写入的,而且即使是提前知道了,也不能保证立马就使用拷贝的空间。所以OS选择了写实拷贝技术,来进行父子进程数据的分离。 

写实拷贝:父子进程共用代码,只有在进行数据写入时才重新内存分布;

为什么选择写实拷贝?

1.用的时候在给你分配空间(高效)

2.操作系统无法在代码执行前预知那些空间会被拷贝

1.我们的代码汇编之后,会有很多行的代码,而且每行代码都加载到内存之后,都有对应的地址;

2.因为进程随时可能被中断(可能并没有执行完),下次回来,还必须保证从之前的位置继续运行,就要求CPU必须随时记录下当前进程执行的位置,所以CPU内有对应的寄存器数据,用来记录当前进程的执行位置!

寄存器在CPU内,只有一份,寄存器内的数据,是可以有多份的 !(进程的上下文)

fork创建子进程的时候,虽然父子进程会各自调度,各种会修改EIP(存储进程代码位置的寄存器),但是已经不重要了,因为子进程已经认为自己的ETI起始值是在fork之后的代码了!!所以子进程会从fork之后的代码开始执行;

写实拷贝:

父子进程代码共享,子进程页表拷贝父进程页表,在子进程或者父进程进行写入操作时,页表发生改变,重新分配内存;

因为有写实拷贝技术的存在,所以,父子进程得以彻底分离,完成了进程独立性的技术保证;

写实拷贝是一种延时申请技术,可以提高整机的内存使用率;

二、进程终止

1.进程终止时,操作系统做了什么?

释放进程中申请的相关内核数据结构和对应数据和代码;(本质就是释放系统资源)

2.进程终止的常见方式?

  • a.代码跑完,结果正确 
  • b.代码跑完,结果不正确
  • c.代码没有跑完,程序崩溃了(信号部分,涉及一点点)

main函数返回值的含义是什么?return 0的含义是什么?为什么总是0?

0:退出码;返回上一进程,用来判断进程执行结果的,可以忽略。

命令:echo $? // 获取上一个进程的退出码

程序崩溃了,退出码无意义。一般而言退出码对应的return语句,没有被执行!

3.用代码,如果终止一个进程?

  • main函数内的return语句就是终止进程的,return 退出码;
  • exit(int status)在任何地方调用,都表示终止进程;(库函数
  • _exit(int status)终止进程;(系统接口直接终止进程,不进行清理函数和缓冲区的刷新)

库函数 vs 系统接口

逐渐底层:语言 -> 库函数 -> 系统接口 -> 操作系统

printf - \n 数据是存放在“缓冲区”的,那么这个”缓冲区“在哪里,谁维护的?

一定不在操作系统内部;如果是操作系统维护的,那么_exit(int statut) 就可以刷新缓冲区;"缓冲区"是C标准库维护的;

三、进程等待

  • 子进程退出,父进程不管子进程,子进程就要处于僵尸状态  ---- 导致内存泄漏
  • 父进程创建子进程,是让子进程办事的,那么子进程把任务完成得怎么样?父进程需要关心吗?如果需要,如何得知?如果不需要,该怎么处理?
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

僵尸状态的进程,即使是kill -9也无法杀死一个已经死去的进程;

如何等待?等待是什么?

wait:回收僵尸进程的问题

参数:wait(int *status)

让父进程来等待子进程退出,并回收

    pid_t ret = wait(NULL);
    if (ret > 0)
    {
         printf("等待子进程成功,ret: %d\n", ret);                           
    }

 

waitpid;获取子进程退出结果的问题

参数:waitpid(pid_t pid, int *status, int option)

  • pid = -1 所有子进程
  • pid > 0 指定的子进程
  • status 获取子进程退出结果(退出码)输出型参数(是按照bit位的方式进行划分的,只使用低16位,高8位表示退出码)
  • option 默认为0 (阻塞态等待)(WNOHANG非阻塞态等待)

进程异常退出,或者崩溃,本质是操作系统将这进程杀掉了 (通过发送信号的方式);

status的低7位bit位,表示进程收到的信号!

程序异常,不仅仅是内部代码的问题,也可能是外部杀掉了你的进程;

  • 父进程通过wait/waitpid可以拿到子进程的退出结果,为什么要用wait/waitpid呢?不直接使用全局变量?(不可以,进程具有独立性,写实拷贝)
  • 既然进程具有独立性的,进程的退出码不也是进程的数据码?父进程又怎么拿到的呢?wait/waitpid究竟做了什么呢?

僵尸进程:至少要保留进程的PCB信息,task_struct里面保留了任何进程退出时的结果信息!可知wait/waitpid本质是读取了子进程的PCB结构体中的信息;

  • wait/waitpid有这个权限吗?

 当然有,它是系统调用,操作系统操作! 

四、进程替换

  • 1、是什么?

fork()后,父子进程各自执行父进程代码的一部分,父子代码共享,数据写时拷贝!

如果子进程不和父进程共享代码,而是想自己执行一个全新的程序,程序替换是通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用进程的地址空间中

原理:

  • 2、怎么办?

1、不创建子进程

  1 #include <iostream>
  2 #include <vector>
  3 #include <unistd.h>
  4 #include <stdio.h>                                                                                                                                                     
  5 
  6 int main()
  7 {
  8     printf("当前进程开始的代码\n");
  9 
 10 
 11     printf("当前进程结束的代码\n");
 12     return 0;
 13 }
~

execl进程替换函数; 

  1 #include <iostream>
  2 #include <vector>
  3 #include <unistd.h>
  4 #include <stdio.h>
  5 
  6 int main()
  7 {
  8     printf("当前进程开始的代码\n");
  9 
 10     execl("/usr/bin/ls", "ls", "-l", NULL);                                                                                                                            
 11 
 12     printf("当前进程结束的代码\n");
 13     return 0;
 14 }

  运行结果

execl是进程替换的函数,调用之后,进程所有的代码都会被替换,之后的代码就不会执行了!!! 

 execl为什么调用成功,没有返回值呢?

调用成功后,将之前进程的返回值等都全部替换了;

2、创建子进程

  简易的xshell代码

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/wait.h>
  4 #include <stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t id = fork();
  9     if (id == 0)
 10     {
 11         //子进程
 12         printf("子进程开始运行: pid:%d\n", getpid());
 13         sleep(2);
 14         execl("/usr/bin/ls", "ls", "-l", NULL);
 15         exit(1);
 16     }
 17     else
 18     {
 19         //父进程
 20         int status = 0;
 21         printf("父进程开始运行: pid:%d\n", getpid());
 22         pid_t id = waitpid(-1, &status, 0);
 23 
 24         if (id > 0)                                                                                                                                                    
 25         {
 26             printf("wait success, exit code: %d\n", WEXITSTATUS(status));
 27         }
 28     }
 29 
 30 

int execv(const  char * path, char *const argv[]); -- 用数组的方式传参数

int execlp(cosnt char* file, char const* arg, ... ); --- 会自己在环境变量中查找

int execlp("ls", "ls", "-a", "-l", "NULL");
前面的"ls"表示要执行谁;
后面的"ls"表示要如何执行;

这些函数本质就是将自己的这些参数,传给可执行程序中的main函数;

int execvp(const char* file, char *const  argv[]); --- 会自己在环境变量中查找

char* const argv[] = { (char*)"ls",(char*)"-l" ,"NULL"};
int execvp("ls", argv); 

int execle(const char* path, const char *arg, ... ,const char* envp[]); 

1.如何执行其他我写的C、C++二进制可执行程序

2.如何执行其他语言的程序

         //子进程
 12         printf("子进程开始运行: pid:%d\n", getpid());
 15         execlp("python", "python", "test.py", NULL);                                                                                                                   
 16         exit(1)

即:exec*就是加载器的底层接口!

环境变量具有全局性

为什么环境变量会有全局性呢?

int execle(const char* path, const char *arg, ... ,const char* envp[]); 

这个函数里面的const char* envp[],就是将父进程的环境变量传给子进程,如此递归下去,环境变量就具有了全局性;

int execve (const char *filename, char *const argv [], char *const envp[]);

真正的系统调用!!之前的都是系统提供的基本封装;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绅士·永

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值