type
status
slug
summary
tags
category
icon
password
new update day
Property
Oct 22, 2023 01:31 PM
created days
Last edited time
Oct 22, 2023 01:31 PM

系统调用简介

操作系统为用户态运行的进程与硬件设备(如 CPU、磁盘、打印机等等)进行交互提供了一组接口。在应用程序和硬件之间设置这样一个接口层具有很多优点,首先,这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来。其次,极大地提高了系统的安全性,内核在要满足某个请求之前就可以在接口级检查这种请求的正确性。最后,更重要的是,这些接口使得程序更具有可移植性,因为只要不同操作系统所提供的一组接口相同,那么在这些操作系统之上就可以正确地编译和执行相同的程序。这组接口就是所谓的“系统调用”。

fork 系统调用

在 Linux 系统中,如何创建一个进程,就是通过调用系统调用 fork()fork() 创建一个新进程,fork() 本身就是分叉的意思,也就是当执行 fork() 以后,一个新的进程就诞生了,也就是父子进程都存在了,执行流就一分为二,fork() 给父进程返回子进程的 pid,给子进程返回 0, 如图所示:
notion image
  1. fork 系统调用头文件:<unistd.h>
  1. fork 系统调用的原型:pid_t fork()
  1. fork 系统调用的返回值:pid_t 是进程描述符类型,本质就是一个 int。如果 fork() 函数执行失败,返回一个负数(<0);如果 fork() 调用执行成功,返回两个值:0 和所创建子进程的 ID。
  1. fork 系统调用的功能:以当前进程作为父进程创建出一个新的子进程,并且将父进程的所有资源拷贝给子进程,这样子进程作为父进程的一个副本存在。父子进程几乎是完全相同的,但子进程与父进程的 ID 不同。

编写实验代码

notion image

编译、链接、运行,并反汇编

也可以用一步编译并链接:
notion image
为什么要反汇编,是为了看到程序执行前的样子到底是什么,也可以反汇编成 Intel 的汇编格式。

执行结果

实验讨论

  1. 编写代码,编译、汇编、链接以及反汇编,说说每一步都做了什么?你真切的学到了什么?
    1. 编写代码,即进行代码文本的编写
    2. 预处理,即将处理预处理指令如包含#include,宏定义制定#define等。
    3. 编译,即将预处理后的代码文件,编译成特定的汇编代码。
    4. 汇编,汇编过程将上一步的汇编代码转换成机器码,这一步产生的文件叫做目标文件,是二进制格式。
    5. 链接过程使用链接器将该目标文件与其他目标文件、库文件、启动文件等链接起来生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
  1. 在第 12 行执行 fork() 时系统进入到什么态?
    1. 内核态。
  1. 结合 PPT 中的 fork() 的执行流,分析 getpid() 的执行流。
    1. 主程序在执行的时候,执行到对应的 getpid() 系统调用,进入到内核态中,将对应的结果返回到用户态之后,完成所有的工作。
  1. fork() 的执行对你有什么启发?
    1. fork() 产生的子进程在父进程退出之后才执行的,通过执行结果我们可以发现,两者的代码都是一样的,确实是父进程的副本。但是为什么是父进程退出之后再执行的子进程,这样的话,父进程还可以对子进程进行回收处理吗?
    2. 答案
      fork() 函数通过系统调用创建一个与原来进程几乎完全相同的进程。在 fork() 之后,父进程和子进程都处于就绪状态,开始由内核调度执行。谁先执行完全由调度器决定1。
      父进程可以通过 wait() 或 waitpid() 函数等待子进程退出,并收集子进程的退出状态。如果子进程退出状态不被收集,则变成僵尸进程。子进程可以通过调用 _exit() 或 exit() 函数来退出。_exit() 函数直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的各种数据结构。exit() 函数在执行退出之前,会将文件缓冲区中的内容写回文件,即清理 I/O 缓冲2。
      您提到的程序输出显示父进程先退出,这是因为父进程和子进程都是独立执行的,它们的执行顺序是不确定的。父进程可以在退出之前对子进程进行回收处理。

代码改进

原来代码的问题

  • 父进程没有执行子进程的退出清理工作,可能会产生僵尸进程。

修改后的代码

执行结果

  • 可以看到确实是两个进程是同时调度的。
  • 父进程使用 wait() 函数来等待子进程退出。当子进程退出时,wait() 函数返回并将子进程的退出状态存储在 status 变量中。父进程可以通过检查 status 变量的值来确定子进程是如何退出的。此外,wait() 函数还会回收子进程的 PCB,从而避免产生僵尸进程。

参考资料

动手实践-内核模块的插入删除如何解决 Windows 10/11 商店安装应用出现0x80073d05错误?