【Linux】进程控制

1. 程序地址空间

在这里插入图片描述

1.1 内存管理和进程管理

966129.png)
我们可以看到的所有地址都是虚拟地址,平时写的C/C++,用的指针,指针里面的地址,全部都不是物理地址。

1.2 子进程的进程管理与内存管理

在这里插入图片描述

  • 写时拷贝是由操作系统自动完成的,操作系统重新开辟空间,但是在这个过程中,页表左侧的虚拟地址是0感知的,一直都是不变的。
  • 页表的 rw 表示该数据的读写属性,即确定了数据是否能修改。
  • 子进程的页表是由父进程拷贝而来,但不同的是,不管父进程 rw 那一栏是什么,子进程页表的 rw 那一栏都是 r 。当子进程要修改数据时,操作系统会辨别数据是否能修改,若能修改,那操作系统会重新开辟空间,进行写时拷贝,并修改 rw 属性

1.3 页表

什么是页表?

页表是一种数据结构,用于管理虚拟内存和物理内存之间的映射关系。在操作系统中,当一个程序需要访问内存时,它会先访问虚拟内存,然后再通过页表将虚拟地址映射到物理内存中的实际地址。

页表的作用是什么?

  1. 让进程以统一的视角看待内存。
  2. 增加进程虚拟地址空间可以让我们访问内存的时候,增加一个转换的过程,在这个转化的过程中,可以对我们的寻址请求进行审查,所以一旦异常访问,直接拦截,该请求不会到达物理内存,保护物理内存
  3. 因为有地址空间和页表的存在,将进程管理模块,和内存管理模块进行解耦合!

页表地址:

页表地址也属于进程上下文,进程运行时,被放在了寄存器了。

1.4 大文件的惰性加载

  1. 概况程序在计算机如何运行:本身程序是在硬盘上,需要把程序加载进内存,然后由CPU去执行​​​​​​​。
  2. 操作系统对大文件可以实现分批加载,意思就是说,先将大文件放在硬盘里,cpu运行时,需要哪部分数据或代码再把哪部分数据或代码加载到内存里,cpu用完后,再把在内存中的这部分数据或代码释放在内存中释放掉。
  3. 缺页中断:当cpu需要某数据或代码时,会去进程地址空间查找虚拟地址,再通过页表查看对应代码或数据有没有加载到内存,如果有,就通过物理地址在内存中查找,如果没有,就在将数据或代码从硬盘加载到内存。当没有被加载的情况,就叫做缺页中断
  4. 那么进程在被创建的时候,是先创建内核数据结构呢? 先加载对应的可执行程序呢?---------先创建内核数据结构。

2. 进程终止

2.1 进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止
  • 对于情况 1 和 2 :统一会采用进程的退出码来进行判定结果的正确性!  
  • 退出码----->return 的不同的返回值数字。
  • main函数 的返回值,本质表示:进程运行完成时是否是正确的结果,如果不是,可以用不同的数字,表示不同的出错原因!
  • 情况3:本质可能就是代码没有跑完,该情况下,进程的退出码无意义

2.2 进程常见退出方法

正常终止:

  1. 从main返回
  2. 调用exit
  3. _exit

可以通过 echo $? 查看上个进程的退出码:
在这里插入图片描述

2.2.1 exit 和 return

exit 和 return 区别:exit 在任意地方被调用,都表示调用进程直接退出,而 return 只表示当前函数返。

2.2.2 _exit 和 exit

_exit 函数和 exit 函数:
在这里插入图片描述

_exit:系统调用接口,不刷新缓存区
exit:库函数调用接口,刷新缓存区

3. 进程等待

3.1 含义

通过系统调用wait/waitpid,来进行对子进程进行状态检测与回收的功能
具体方法:父进程通过调用wait/waitpid进行僵尸进程的回收问题。

3.2 进程等待必要性

  • 父进程如果不管不顾,不进行进程等待,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 有时父进程需要知道,父进程派给子进程的任务完成的如何。父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

3.3 进程等待的方法

3.3.1 wait

  • pid_t wait(int*status);

  • 头文件:#include<sys/types.h>
          #include<sys/wait.h>

  • 返回值:
    成功返回被等待进程:pid,失败返回:-1

  • 参数:
    输出型参数,status 获取子进程退出状态,不关心则可以设置成为 NULL

3.3.2 waitpid

  • pid_ t waitpid(pid_t pid, int *status, int options);
  • 返回值:
    • 成功:当正常返回的时候waitpid返回收集到的子进程ID
    • 失败:如果调用中出错,则返回 -1 ,这时 errno 会被设置成相应的值以指示错误所在;
    • 子进程未僵尸:如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回 0
  • 参数:
    • pid:
      • Pid= -1,等待任一个子进程。与wait等效。
      • Pid > 0,等待其进程ID与pid相等的子进程。
    • status:
      • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
      • WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
      • 如果调用时,status 传递的是 NULL,表示不关心子进程的退出状态信息
    • options:
      • WNOHANG : 若函数调用时options == WNOHANG----------那么,若pid指定的子进程没有结束,则waitpid()函数返回 0 ,不予以等待;若正常结束,则返回该子进程的ID;若调用失败,则返回 -1。
      • 其他:若函数调用时,options 为其他值--------那么,若pid指定的子进程没有结束,则waitpid() 函数会一直等待;若正常结束,则返回该子进程的ID;若调用失败,则返回 -1。

注意:

  • waitpid() 函数调用失败,可能是不存在 pid为 参数pid 的进程。
  • 如果子进程不退出,父进程调用了 wait 和 waitpid 函数的时候,且函数没有返回,这种情况叫做阻塞状态!

3.3.3 status 的切割

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • status不能简单的当作整形来看待,且只有 status 的低16比特位有意义,status可以当作位图来看待:

3.4 进程等待的过程

在这里插入图片描述

4. 进程程序替换

4.1 替换原理

  • 当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,页表也会更新,从新程序的启动例程开始执行,新程序之后的旧程序不会执行了
  • 调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

4.2 替换函数

  • 其实有六种以exec开头的函数,统称exec函数。
  • exec函数又分为系统调用函数和库调用函数。5个库调用函数 到最后其实也是转变为 那一个库调用函数。
  • 库调用:
#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[]);

4.3 替换函数返回值

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

4.4 函数参数理解

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH,不用带路径
  • e(env) : 表示自己维护环境变量

在这里插入图片描述

exec调用举例如下:

#include <unistd.h>
int main()
{
 char *const argv[] = {"ps", "-ef", NULL};
 char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
 
 execl("/bin/ps", "ps", "-ef", NULL);
 // 带p的,可以使用环境变量PATH,无需写全路径
 execlp("ps", "ps", "-ef", NULL);
 // 带e的,需要自己组装环境变量
 execle("ps", "ps", "-ef", NULL, envp);
 execv("/bin/ps", argv);
 
 // 带p的,可以使用环境变量PATH,无需写全路径
 execvp("ps", argv);
 // 带e的,需要自己组装环境变量
 execve("/bin/ps", argv, envp);
 
 exit(0);
}

5. 做一个简易的shell

代码放在gitee里了:shell代码链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值