【Linux】进程控制

2023-12-13 18:42:44

👑作者主页:@安 度 因
🏠学习社区:安度因
📖专栏链接:Linux

重谈进程创建

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程 。

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程(pcb)到系统进程列表当中
  • fork返回,开始调度器调度

执行到 fork 语句时,进入操作系统内核空间执行fork:

image-20231128200754247

创建进程:

image-20231128201842901

写时拷贝

os如何知道父子进程共享数据块(父子进程对应数据,代码和数据中的数据)需要写时拷贝?

image-20231128205656025

父进程创建子进程后,父进程可读的部分权限不变,默认把父进程对应读写的权限都改为只读;子进程也是只读。

此刻父子任意一个进行写入,进行权限审核时,如果OS识别历史上这块区域可以被使用只是被暂时设置为只读状态,此刻触发缺页中断,写时拷贝,重新开辟空间,拷贝数据, 把父子进程对应页表中的只读标签去掉,此刻就可以进行访问了。 (原先权限就是读的不会有这种情况)。

写时拷贝是父进程创建子进程时,让父进程对数据区的页表的权限改成只读权限,看后续谁先触发写入的错误,来判断谁要发生写时拷贝。

该理论只适用于数据,不适用于代码。

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段(if else)。例如,父进程等待客户端请求,生成子进程来处理请求。

  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

这种情况下,系统可能会杀进程

创建多个进程

exit 退出进程

image-20231204202845545

脚本:

while :; do ps ajx | head -1 && ps axj | grep myproc | grep -v grep; echo "---------------------"; sleep 1; done

image-20231204203310319

父子进程谁先运行由调度器决定(我们不可知),取决于进程创建成功之后,放入运行队列的位置,谁先被放,谁先调度:

image-20231204203603277

进程终止

进程退出情况

  • 代码运行完毕,结果正确(本文重要)–> 不关心为什么正确
  • 代码运行完毕,结果不正确(本文重要)–> 关心为什么不正确?
  • 代码异常终止( 进程等待讲一部分,信号将一部分)
return 0;

进程的退出码,表征进程的运行结果是否正确 0–>success,0 会被父进程(bash)拿到;非 0 表示错的。

进程中,谁会关心我运行的情况?

一般而言,是该进程的父进程关心

当代码运行完毕后,结果不正确,可以通过 return 的不同的返回值数字,表征不同的出错原因 —— 退出码

结论1:main函数的返回值,本质表示:进程运行完成时,是否正确的结果,如果不是可以用不同的数字表示不同的出错原因。


echo $? 第一次接收是进程返回的退出码,后来是 0 ,为什么?

注:$? 表示命令行中最近一个程序运行时的退出码

当 echo 过一次后,之后最近一次程序就是 echo 命令,echo 命令执行正确,所以退出码为 0

image-20231204213703884

在不进行打印的代码中,比如向网络中写入时,进程运行完之后,并没有返回任何信息,不能知道结果,此时只能用退出码。

退出码是计算机看的,错误信息是人看的。

strerror 会带回错误码对应的错误信息:

image-20231205184220487

image-20231205184321809

例如 ls 有问题,打印错误码:

image-20231205184959150

系统提供的错误码和错误信息,描述是互相关心的。

可以自己弄一套。

父进程为什么要关心子进程的运行情况?

因为用户需要知道出错原因,需要父进程把退出信息转交给用户,让用户决策下一阶段的执行任务。

回顾王婆的例子:

从这里就可以知道,王婆是父进程,村长是用户,实习生是子进程,子进程回来会带回来执行的结果,如果错误,会有错误的原因。村长需要知道叫王婆办事办的怎么样,王婆会把实习生工作的结果给村长汇报一下。一般是要让实习生把结果反馈给王婆或者村长的,除非有特殊情况王婆不关心,此刻退出码随便写。

总结

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确

正确与否会通过进程的退出码来标识,即 main 函数的返回值。

errno c 提供的全局变量,保存的是最近一次的错误码


异常时,退出码还有意义?

代码异常,本质可能就是代码没跑完;可能根本没有执行到 return 语句,所以退出码没有意义;但是如果刚把 return 执行完呢?main函数是被调用的,return 在语言层面上程序结束了,但是并不一定进程退出了。说到底,我怎么知道 return 有没有被执行?再者说,return 执行异常的情况,退出码我们敢用吗? 我们并不能确定在哪里异常,随时随地可能发生,异常时所以退出码没有意义。此刻不关心退出码。

所以先关心进程退出是否异常,再看结果是否正确(再关心退出码)

进程出异常,本质是我们的进程收到了对应的信号,异常会触发某种硬件错误(野指针,/0),这些硬件问题都会被 OS 识别,向进程发信号完成

image-20231205202300275

浮点数报错:SIGFPE

程序异常后,如何证明 OS 将异常转换成信号,发送给进程,让进程退出的?

当写一个循环的代码:

image-20231205203610374

如果向它发送信号 8 即浮点数报错:

image-20231205203710187

证明结论:进程出异常,本质是我们的进程收到了对应的信号

所以如果父进程要关心子进程是否退出,只需要确定父进程退出时是否收到过信号,然后再查对应的结果,如果错误,得到错误原因。

进程常见退出方式

正常终止:

  • main 函数返回
  • exit
  • _exit

exit:引起一个进程终止

image-20231205211016755

exit 的参数是当前进程对应的退出码

image-20231205211416600

return 在 main 函数表示退出,在其他函数表示函数返回;exit 在任何函数中都是退出


_exit 为系统调用

_exit 和 exit 有什么区别?

int main()
{
    printf("you can see me!\n");
    exit(11); // _exit(11);
}

此刻没区别

int main()
{
    printf("you can see me!");
    exit(11); // _exit(11);
}

_exit 不能打印出缓冲区中的数据。

_exit 在 OS 内部会直接终止进程;exit 终止进程之前会冲刷缓冲区,关闭流,所以 exit 可以让我们看到刷新的结果

_exit 是系统调用,认为程序调用接口,直接在进程场面上终止进程了;exit 会将应用层中的东西刷新,再调用 _exit

两者是调用和被调用的关系。

image-20231206143319534

故 printf 一定是先把数据写入缓冲区中,合适的时候,再进行刷新。那么这个缓冲区绝对不在哪里?

决定不在内核里!因为如果在内核中,_exit 终止的时候,会把内核中缓冲区的数据也做一下刷新,因为 OS 不做浪费资源的事情,如果不刷新,为什么要维护缓冲区把数据保存起来呢?所以一定不在内核中。

文章来源:https://blog.csdn.net/m0_67867172/article/details/134831089
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。