✍个人博客:https://blog.csdn.net/Newin2020?type=blog
📣专栏地址:https://blog.csdn.net/newin2020/category_12820365.html
📚专栏简介:在这个专栏中,我将会分享操作系统面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
52. 多进程模型
每一个进程是资源分配的基本单位。
进程结构由以下几个部分组成:代码段、堆栈段、数据段。代码段是静态的二进制代码,多个程序可以共享。
实际上在父进程创建子进程之后,父、子进程除了 pid 外,几乎所有的部分几乎一样。
父、子进程共享全部数据,但并不是说他们就是对同一块数据进行操作,子进程在读写数据时会通过写时复制机制将公共的数据重新拷贝一份,之后在拷贝出的数据上进行操作。
如果子进程想要运行自己的代码段,还可以通过调用 execv() 函数重新加载新的代码段,之后就和父进程独立开了。
我们在 shell 中执行程序就是通过 shell 进程先 fork() 一个子进程再通过 execv() 重新加载新的代码段的过程。
53. 多线程创建与结束
-
背景知识:
进程有两种创建方式,一种是操作系统创建的一种是父进程创建的。从计算机启动到终端执行程序的过程为:0 号进程 -> 1 号内核进程 -> 1 号用户进程 (init 进程) -> getty 进程 -> shell 进程 -> 命令行执行进程。所以我们在命令行中通过 ./program 执行可执行文件时,所有创建的进程都是 shell 进程的子进程,这也就是为什么 shell 一关闭,在 shell 中执行的进程都自动被关闭的原因。从 shell 进程到创建其他子进程需要通过以下接口。
-
相关接口:
-
创建进程:pid_t fork(void);
返回值:出错返回 -1;父进程中返回 pid > 0;子进程中 pid == 0
-
结束进程:void exit(int status);
- status 是退出状态,保存在全局变量中,通常 0 表示正常退出。
-
获得 PID:pid_t getpid(void);
返回调用者 pid。
-
获得父进程 PID:pid_t getppid(void);
返回父进程 pid。
-
-
其他补充:
- 正常退出方式:exit()、_exit()、return(在 main 中)。
- 异常退出方式:abort()、终止信号。
54. Linux 进程控制
-
进程地址空间(地址空间)
虚拟存储器为每个进程提供了独占系统地址空间的假象。
尽管每个进程地址空间内容不尽相同,但是他们的都有相似的结构。X86 Linux 进程的地址空间底部是保留给用户程序的,包括文本、数据、堆、栈等,其中文本区和数据区是通过存储器映射方式将磁盘中可执行文件的相应段映射至虚拟存储器地址空间中。
有一些 "敏感" 的地址需要注意下,对于 32 位进程来说,代码段从 0x08048000 开始。从 0xC0000000 开始到 0xFFFFFFFF 是内核地址空间,通常情况下代码运行在用户态(使用 0x00000000 ~ 0xC00000000 的用户地址空间),当发生系统调用、进程切换等操作时 CPU 控制寄存器设置模式位,进入内核模式,在该状态(超级用户模式)下进程可以访问全部存储器位置和执行全部指令。
也就说 32 位进程的地址空间都是 4G,但用户态下只能访问低 3G 的地址空间,若要访问 3G ~ 4G 的地址空间则只有进入内核态才行。
-
进程控制块(处理机)
进程的调度实际就是内核选择相应的进程控制块,被选择的进程控制块中包含了一个进程基本的信息。
-
上下文切换
内核管理所有进程控制块,而进程控制块记录了进程全部状态信息。每一次进程调度就是一次上下文切换,所谓的上下文本质上就是当前运行状态,主要包括通用寄存器、浮点寄存器、状态寄存器、程序计数器、用户栈和内核数据结构(页表、进程表、文件表)等。
进程执行时刻,内核可以决定抢占当前进程并开始新的进程,这个过程由内核调度器完成,当调度器选择了某个进程时称为该进程被调度,该过程通过上下文切换来改变当前状态。
一次完整的上下文切换通常是进程原先运行于用户态,之后因系统调用或时间片到切换到内核态执行内核指令,完成上下文切换后回到用户态,此时已经切换到进程 B。