首页 > 系统相关 >第2讲:进程管理

第2讲:进程管理

时间:2024-06-10 10:22:37浏览次数:22  
标签:task struct 管理 空间 线程 内核 进程

本文的主要内容:一个进程从生到死的过程。

一、任务队列和 task_struct 任务描述符

Linux的“任务队列”是一个双向链表,链表中每一项为进程描述符task_struct,它包含了一个正在执行的程序的完整信息:它打开的文件、进程的地址空间、挂起的信号、进程的状态等等。

  • Linux 通过 slab 分配器分配 task_struct,并实现“对象复用”和“缓存着色”(“着色”是为了能重复使用 task_struct,类似于线程池中复用线程的设计)。
  • “进程内核栈”并不直接管理 task_struct 结构体,而是将其指针放到一个 struct thread_info 这个新结构体中。查找时使用 current 宏先找到 thread_info,就能找到task_struct 指针从而确定当前正在执行的进程。
  • 每个进程都有自己唯一的PID(Process Identification Value)。它的最大值决定了系统中允许同时存在的最大进程数,一般为short int的最大值 32768(位于<linux/threads.h>,可以通过 /proc/sys/kernel/pid_max 来修改)。
  • 通过 set_task_state(task, state) 来设置进程的状态。

(一)进程上下文

用户进程执行系统调用(或中断异常)而“陷入内核空间”,此时内核代表进程执行,也就是处于“进程上下文”中。因此“进程上下文”包括两部分资源:

  • 用户空间:虚拟内存空间(包括栈、堆、全局区、代码区等资源)。“用户空间资源的切换”本质上是“页表的切换”,这也是造成线程和进程之间性能的差异的关键。
  • 内核空间:堆栈、寄存器等资源。“内核空间资源的切换”本质上是维护新的 PCB 控制块和程序计数器等资源。

“中断上下文”中,系统不代表进程执行,而是执行一个中断处理程序。不会有进程干扰这些中断处理程序,所以此时不存在进程上下文。

(二)进程家族树

Linux系统的进程之间存在一个明显的继承关系。

  • 0号进程:pid=0的内核调度进程,在系统启动后,它负责 CPU 调度和其他低级任务。

这个进程通常是内核的一部分,而不是一个常规的用户空间进程,因此零号进程不能被杀死(kill)。

  • 1号进程:用户空间的第一个进程,也是initsystemd进程。由内核在系统启动的最后阶段启动init进程,负责初始化系统的其他部分、启动所有其他用户空间的进程。是用户空间所有孤立进程的父进程。

init 进程的描述符是单独静态分配的 init_task,可以通过 task ?= &init_task 判断当前指针是否执行 init 进程。

用户空间所有进程都是1号进程的后代。每个进程也可以拥有多个子进程。task_stuct 中有struct task_struct *parent 指向父进程;还有一个 children 子进程链表。

二、进程创建:fork调用

内核是如何fork出一个进程的

从源码的角度理解操作系统进程

Linux进程创建分解为 fork 和 exec 两步。fork 拷贝当前进程创建一个子进程,exec 读入可执行文件并载入地址空间开始运行(实际上还得调度器说了算)。

fork 的工作流程

fork, vfork, __clone 的底层都是 clone 系统调用,唯一的区分是传入的参数不同,从而执行不同的操作。

clone 会调用 do_fork,再调用 copy_process 完成:

  1. dup_task_struct拷贝当前进程,包括内核栈、thread_info 和 task_struct,此时父子进程是完全相同的。
  2. 区分父子进程。将子进程 task_struct 内的统计信息清零(比如挂起的信号就没必要继承)
  3. 子进程的状态设为 task_uninterruptible
  4. 更新子进程的 flags 标志。比如超级权限 PF_SUPERPRIV、是否调用了 exec 函数 PF_FORKNOEXEC
  5. 调用 alloc_pid 为子进程分配新的 PID,PPID 为父进程。
  6. 资源处理。根据传入的参数不同,拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间等。
  7. 完成扫尾工作,返回一个指向子进程的指针。

do_fork收到copy_process完成后,会唤醒子进程并优先让其运行。这是出于“写时拷贝策略”的考虑,如果父进程先执行可能会向地址空间写入数据,导致还需要另外的拷贝。

COW:只有在需要写入(修改数据)时,才会拷贝,使各自进程拥有自己的拷贝,否则就以只读方式共享。最大的优势就是 fork 的实际开销就是复制父进程的页表、给子进程创建唯一的进程描述符,确保进程能快速创建、执行。

vfork 最大的不同是:父进程会被阻塞,直到收到 vfork_done 信号(子进程退出或执行 exec)。这是个很老的设计了,因为原来没有COW策略,通过这种设计可以让子进程优先运行。并且 vfork 需要等待信号,如果等不到(exec 调用失败)那么父进程会被一直阻塞。

三、线程

线程在Linux中就是个共享某些资源的进程。因此线程在创建时,传入 clone 函数的参数指定了共享资源,比如clone_vm,clone_fs,clone_files,clone_sighand等。

线程也是有数量限制的:因为每个线程都要占据堆栈空间,而物理内存不是无限的。可以使用ulimit -n来查看。

内核线程为什么没有独立的地址空间?

内核线程是独立运行在内核空间的标准进程,并且它没有独立的地址空间(指向地址空间的 mm 指针被设置为 NULL)。

  • 性能考虑:拥有独立地址空间意味着上下文切换(context switch)时需要更改页表和刷新 TLB(Translation Lookaside Buffer),这是一个相对耗时的操作。
  • 简化设计:内核线程主要用于内核级任务,这些任务通常不需要访问用户空间数据,也不需要保护以防止其他进程或线程的干扰。因此,没有必要给它们分配独立的地址空间。
  • 资源共享:内核线程需要访问全局内核数据结构,这些结构存在于内核地址空间中。如果内核线程有自己独立的地址空间,那么访问这些全局数据结构将变得更加复杂和耗时。
  • 内核简洁和一致性:不使用独立的地址空间可以减少内核的复杂性,同时也更容易保证代码的一致性和可维护性。
  • 目的和作用不同:用户空间进程和内核线程的目的和作用不同。用户空间进程用于执行用户级代码,而内核线程用于执行内核级任务。后者通常不需要像前者那样的隔离和保护。
  • 内核状态共享:内核线程通常需要共享某种状态或资源,如文件描述符、内核参数等。如果每个内核线程都有自己的地址空间,这种共享将变得更加复杂。

它们只在内核空间运行,从不切换到用户空间中去。它们的父进程是 kthread 内核线程,也是通过 clone 系统调用完成创建。

四、进程终结

调用 exit() 结束进程,它的底层是 do_exit() 调用。过程如下:

  • 设置 PF_EXITING 标志、删除内核定时器。
  • 调用 exit_mm() 释放进程占用的 mm_struct。
  • 释放资源。exit_files(), exit_fs() 递减文件描述符、系统数据的引用计数(为零则可以释放)。
  • 执行exit_notify通知父进程,寻找养父(线程组的其他线程或init进程),并设置状态为EXIT_ZOMBIE,代表其用不被调用。此时它也就剩下:内核栈、thread_info 和 task_struct 信息了,这是给父进程清理用的。
  • 调用 schedule 切换到新进程,do_exit 函数永不返回。

最后,父进程调用wait()清理残余的(处于EXIT_ZOMBIE状态)进程描述符。

孤儿进程 & 僵尸进程

如果父进程先于子进程退出,那么这些子进程就变成了“孤儿进程”,如果没有新的“养父”进程来回收它们,这些资源就会一直占用,导致资源泄漏。

在子进程退出后,内核仍会保留子进程的一些信息(如退出状态)供父进程查询。如果父进程不调用 wait() 来获取这些信息,那么这些进程会变成“僵尸进程”。

统一将孤儿进程的父进程设置为 init 进程的优势:

  • 可以简化进程管理的逻辑。init 进程(或者 systemd)被设计为能够正确处理这些孤儿进程,包括回收它们的资源和处理它们的退出状态。
  • 确保进程状态的一致性。即便父进程退出了也不影响子进程的运行状态。
  • 通过自动处理这些孤儿进程,可以提高系统的健壮性,即使在不太理想的情况下(例如,某些进程意外退出)。

标签:task,struct,管理,空间,线程,内核,进程
From: https://www.cnblogs.com/7ytr5/p/18236740

相关文章

  • 芯片产业管理和营销指北(4)—— 产品线经理的修行
    本篇是系列最后一篇,本系统所有内容均来自俞志宏老师的《我在硅谷管芯片:芯片产品线经理生存指南》一书的总结整理。工程师工作比较线性,需要深挖专业知识,但也仅需要专注于专业知识。通常的工作内容是::设计某个电路,测试某些参数,解决某个故障产品市场分析人员市场分析因为涉及......
  • SQL Server Management Studio (SSMS) 20.1 - 微软数据库管理工具
    SQLServerManagementStudio(SSMS)20.1-微软数据库管理工具请访问原文链接:https://sysin.org/blog/ssms/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.org笔者注:SQLServer2014及之前版本内置SQLServerManagementStudio(SSMS),SQLServer2016及以后版本......
  • python系列:FastAPI系列 10-路由管理APIRouter
    FastAPI系列10-路由管理APIRouterFastAPI系列10-路由管理APIRouter前言一、路由管理APIRouter二、FastAPI主体总结FastAPI系列10-路由管理APIRouter前言在fastapi中也有类似的功能通过APIRouter来管理一、路由管理APIRouter正在开发一个应用程序或We......
  • mac python 包管理工具 pip 的配置
     python3--versionPython3.12.3brewinstallpython@3.12pip3configsetglobal.index-urlhttps://pypi.tuna.tsinghua.edu.cn/simplepip3configsetglobal.break-system-packagestrue pip3installaiohttppython包管理工具pip的配置 近几年来,python的包......
  • C++ MPI多进程并发
    下载用法mpiexec-n8$PROCESS_COUNTx64\Debug\$TARGET.exe 多进程并发启动mpiexec-fhosts.txt-n3$PROCESS_COUNTx64\Debug\$TARGET.exe  联机并发进程,其它联机电脑需在相同路径下有所有程序//hosts.txt192.168.86.16192.168.86.123192.168.86.108De......
  • “另一个程序已锁定文件的一部分,进程无法访问 打不开磁盘“G:\Ubuntu20.04.3\Ubuntu
    文章目录前言:一、删除lck文件二、移除挂载硬盘总结:前言:在重新刷了系统进行对虚拟机移植的过程中我遇到了“另一个程序已锁定文件的一部分,进程无法访问打不开磁盘"G:\Ubuntu20.04.3\Ubuntu20.04.3.vmdk"或它所依赖的某个快照磁盘……”的问题,因为情况慌乱,所以我没......
  • 零基础非科班也能掌握的C语言知识19 动态内存管理
    动态内存管理1.为什么要有动态内存分配2.malloc和free2.1malloc2.2free3.calloc和realloc3.1calloc3.2realloc4.常见的动态内存的错误4.1对NULL指针的解引用操作4.2对动态开辟空间的越界访问4.3对非动态内存开辟的空间free4.4使用free释放⼀块动态开辟内存的⼀部分4......
  • 硬核!使用jsp+servlet+mysql从0搭建图书管理系统(一)
    一、写在开头1.本项目使用IDLE搭建,使用的数据库是mysql8.02.项目前端代码样式可以自行编写,本文注重的是servlet的逻辑处理3.项目使用的数据库和表如下1)bookinfo表2)borrwos(借阅表)3)managers(管理员表)4)readers(读者表)需要表的小伙伴可以根据对应字段自行创建二、......
  • springboot+vue养老院管理系统附带文章和源代码部署讲解等
    文章目录前言项目运行效果截图技术栈后端springboot框架:后端mybatis框架:前端框架vue:数据库mysql:开发环境代码参考数据库参考源码质量保障源码获取前言......
  • springboot+vue医院管理系统附带文章和源代码部署讲解等
    文章目录前言项目运行效果截图技术栈后端springboot框架:后端mybatis框架:前端框架vue:数据库mysql:开发环境代码参考数据库参考源码质量保障源码获取前言......