《Linux 内核设计与实现》03. 进程管理

news2024/9/23 21:22:13

文章目录

    • 进程描述符及任务结构
      • 分配进程描述符
      • 进程描述符的存放
      • 进程状态
      • 设置当前进程状态
      • 进程上下文
      • 进程家族树
    • 进程创建
    • 线程在 Linux 中的实现
      • 创建线程
      • 内核线程
    • 进程终结
        • 删除进程描述符
        • 孤儿进程

进程描述符及任务结构

内核把进程存放在任务队列(task list)中,该队列由双向循环链表实现。

链表中的每个元素都是 task_struct 类型,也称为进程描述符。

// include/linux/sched.h
struct task_struct {
    unsigned long state;
    int prio;
    unsigned long policy;
    struct task_struct *parent;
    struct list_head tasks;
    pid_t pid;
    ...
}

进程的另一个名字是任务(task)。

分配进程描述符

Linux 通过 slab 分配器分配 task_struct 结构,这样能达到对象复用和缓存着色的目的。

各个进程的 task_struct 存放在它们内核栈的尾端。目的是为了让像 x86 那样寄存器少的硬件体系结构只需要通过栈指针就能够计算出某个进程的位置,从而避免使用额外的寄存器专门记录。

对于栈是向下增长的来说,就将 thread_info 放在栈底,而对于栈是向上增长的来说,就放在栈顶。

关于 thread_info 结构:

// asm/thread.info.h
struct thread_info {
	struct pcb_struct	pcb;		/* palcode state */

	struct task_struct	*task;		/* main task structure */
	unsigned int		flags;		/* low level flags */
	unsigned int		ieee_state;	/* see fpu.h */

	struct exec_domain	*exec_domain;	/* execution domain */
	mm_segment_t		addr_limit;	   /* thread address space */
	unsigned		cpu;		/* current CPU */
	int			preempt_count; /* 0 => preemptable, <0 => BUG */

	int bpt_nsaved;
	unsigned long bpt_addr[2];		/* breakpoint handling  */
	unsigned int bpt_insn[2];

	struct restart_block	restart_block;
};

进程描述符的存放

PID 是内核用来标识一个进程的唯一方式。PID 是一个整数,为了与老版本兼容,它最大值默认被设置为 32768。

PID 最大值可以通过 /proc/sys/kernel/pid_max 来修改上限。

PID 被内核放在了进程描述符(task_struct)中。

在内核中,访问任务需要获得指向其 task_struct 的指针。所以如果内核要执行某个任务,就必须先得到指向其 task_struct 的指针。Linux 中通过 current 宏来实现,硬件体系结构不一样,该宏实现的方式也不同:

  • 若硬件体系结构的寄存器足够,那么就可以将 task_struct 指针直接存储到该寄存器。
  • 若硬件体系结构的寄存器有限,那么就可以在内核栈的尾端创建一个 thread_info 结构,通过计算偏移间接查找 task_struct 结构。

current 宏在 x86 中实现如下:

arch/alpha/include/asm/current.h:

#ifndef _ALPHA_CURRENT_H
#define _ALPHA_CURRENT_H

#include <linux/thread_info.h>

// 通过 current_thread_info() 得到当前进程的指针后直接访问其对应的任务进程(task_struct)指针 task
#define get_current()	(current_thread_info()->task)
#define current		get_current()

#endif /* _ALPHA_CURRENT_H */

arch/alpha/include/asm/thread_info.h:

// 通知编译器将指向 thread_info 类型的指针存放到寄存器 $8 中
register struct thread_info *__current_thread_info __asm__("$8");
// 获取当前进程信息(thread_info)指针
#define current_thread_info()  __current_thread_info

进程状态

位于进程描述符中的 state 域中,每个进程无论何时都有其中一种状态。

  • TASK_RUNNING
  • TASK_INTERRUPTIBLE
  • TASK_UNINTERRUPTIBLE
  • __TASK_TRACED
  • __TASK_STOPPED

设置当前进程状态

  • set_current_state(state):将当前进程设置为 state 状态。
  • set_task_state(tsk, state):将 tsk 进程设置为 state 状态。
#define __set_task_state(tsk, state_value) 	do { (tsk)->state = (state_value); } while (0)
#define set_task_state(tsk, state_value) 	set_mb((tsk)->state, (state_value))

/*
 * set_current_state() includes a barrier so that the write of current->state
 * is correctly serialised wrt the caller's subsequent test of whether to
 * actually sleep:
 *
 *	set_current_state(TASK_UNINTERRUPTIBLE);
 *	if (do_i_need_to_sleep())
 *		schedule();
 *
 * If the caller does not need such serialisation then use __set_current_state()
 */
#define __set_current_state(state_value) do { current->state = (state_value); } while (0)
#define set_current_state(state_value) set_mb(current->state, (state_value))

进程上下文

例如一个在用户空间中运行的程序(可执行程序代码是进程的主要组成部分,这些代码从一个可执行文件载入到进程的地址空间执行),一般程序都是在用户空间中执行。当一个程序在运行过程中调用了系统调用或者触发了某个异常,此时它就会陷入内核空间。此时内核需要代替用户程序执行用户程序所需的程序(其实就是说,用户程序没权限去得到或执行内核的东西,但是我用户程序需要内核的部分东西来辅助用户程序的执行,所以我需要内核来帮我去执行某些程序,最后将结果给我),这便是“代表进程执行”。

用户程序陷入内核后,后面代码不会执行,而是先去内核执行对应的程序,此时用户程序的执行环境便是上下文。

进程家族树

Unix 和 Linux 的进程之间都存在一个明显的继承关系,所有的进程都是 PID 为 1 的 init 进程的后代。待内核一切准备就绪后,便会执行 init 进程来初始化系统所需的资源。

系统中每个进程必有一个父进程(init),而每个进程可以有零个或多个子进程。

struct task_struct {
    struct task_struct *parent;   // 父进程
    struct task_struct *children; // 子进程
};

获取和遍历进程的方式:include/linux/list.h

 /**
 * list_entry - get the struct for this entry
 * @ptr:	the &struct list_head pointer.
 * @type:	the type of the struct this is embedded in.
 * @member:	the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

/**
 * list_for_each	-	iterate over a list
 * @pos:	the &struct list_head to use as a loop cursor.
 * @head:	the head for your list.
 */
#define list_for_each(pos, head) \
	for (pos = (head)->next; prefetch(pos->next), pos != (head); \
        	pos = pos->next)

如果在一个拥有大量进程的系统中遍历所有进程,代价是很大的,因此尽量不要这么做。

进程创建

其它操作系统都提供了产生进程的机制,共两步:

  1. 在新的地址空间中创建进程,读入可执行文件。
  2. 开始执行可执行文件。

Unix 将上面两个步骤分别封装到了 fork()exec() ,这两个函数组合起来使用便和其它操作系统使用单一函数创建进程一样:

  • fork():通过拷贝当前进程生成一个子进程。
  • exec():负责读取可执行文件并将其载入地址空间开始运行。

Linux 的 fork() 使用写时拷贝页实现。

Linux 通过 clone() 系统调用实现 fork()。

线程在 Linux 中的实现

Linux 把所有线程都当进程来实现。线程仅仅被视为一个与其它进程共享某些资源的进程。线程也有自己的 task_struct 只不过它们都共享父进程的地址空间,也就是说它们没有自己的地址空间。在其它操作系统中,线程被称为“轻量级进程”,可在 Linux 中进程本就够轻量了。

创建线程

和创建进程一样,需要调用 clone() 系统调用来实现,不过需要传递一些参数标志来指明需要共享的资源:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

传递给 clone() 的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源种类。

image-20230414214902669

内核线程

内核进程需要在后台执行一些操作。这种任务可以通过内核线程完成 —— 独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间(即 task_struct 中指向地址空间的指针 mm 为 NULL)。它们只在内核空间运行,不会跨越到用户态。

内核线程只能通过其它内核线程来创建。

从现有内核线程中创建一个新的内核线程的方法在 linux/kthread.h 中的 kthread_create()。

进程终结

当一个进程终结时,内核必须释放它所占有的所有资源,并通过父进程。

终结通过 exit() 系统调用来实现,具体实现靠 do_exit(),位于 kernel/exit.c 中。

此时只是释放所占为的内存资源,即对于内存资源位图需要重置。

删除进程描述符

调用完 exit() 后,对应的内存位图被重置,但是此时并没有将进程描述符 task_struct 以及 thread_info 给删除掉,由此可见资源的释放和进程描述符的删除是被分开执行的。

这样做是为了当线程僵尸后,可以得到该线程的信息,以便于通知父进程。当父进程得到了子进程以及死亡的消息后,在来删除进程描述符和 thread_info。

孤儿进程

如果父进程在子进程退出之前就先退出了,必须要有机制保证子进程能找到一个新的父进程,否则这些孤儿进程就会在退出时永远处于僵死状态,白白的消耗内存。

解决方案是给子进程在当前进程组内找一个线程作为父进程,实在不行,就让 init 作为它们的父进程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498790.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL高级--锁

一、锁 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题…

新手必看:腾讯云轻量服务器使用限制

腾讯云轻量应用服务器和云服务器CVM相比具有一些限制&#xff0c;比如轻量服务器不支持更换内网IP地址&#xff0c;轻量服务器只能套餐整体升级且不支持降配&#xff0c;轻量不支持用户自定义配置私有网络VPC&#xff0c;还有如实例配额、云硬盘配额、备案限制和内网连通性等限…

整理一下最近了解到的AIGC工具

AIGC工具的一点整理 前言AIGC类型图像生成类Stable diffusionMidjourneyDALLE 2三种工具比较DeepFloyd IF 文本生成语音生成So-vits-svc 4.0 结尾 前言 好久没有写csdn博客了&#xff0c;突然不知道写点什么&#xff0c;最近AIGC真的很火&#xff0c;有一种三天不看就跟不上发…

计算机系统-异常控制流

例行前言&#xff1a; 本篇不是学习课程时的笔记&#xff0c;是重看这本书时的简记。对于学习本课程的同学&#xff0c;未涉及的内容不代表考试不涉及。核心内容是信号部分。本章内容介绍了较多的信号处理函数&#xff0c;需要在实验中巩固本章所学内容及相关问题的处理(并发&…

五彩斑斓的黑:Fun with PyQt5+CMake+C++

Fun Pain Fun with PyQt5CMakeC 本文相关代码GitCode地址 这个项目与PyQt5只有半毛钱关系。事情是这样发生的。当时&#xff0c;我在一个新电脑上干活&#xff0c;装了miniconda&#xff0c;装了PyQt5&#xff0c;干着干着突然要整一个Qt5。我想也挺好&#xff0c;据说C 17里…

Node.js和在浏览器之中的不同

在Node.js中编写JavaScript应用程序与在浏览器中为Web编程有何不同 1、在浏览器中&#xff0c;大多数时候您所做的是与DOM或其他Web平台API&#xff08;如Cookie&#xff09;进行交互。当然&#xff0c;Node.js中并不存在这些。您没有浏览器提供的文档、窗口和所有其他对象。 …

让测试更轻松:学习Selenium进行Web应用程序自动化测试

B站首推&#xff01;2023最详细自动化测试合集&#xff0c;小白皆可掌握&#xff0c;让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 摘要&#xff1a; 什么是Selenium 安装Selenium 编写自动化测试脚本 第一步&#xff1a;导入Selenium库 …

前端006_头部快捷导航_标签导航栏

效果如下,红色方框里面有快捷导航 1、添加标签栏导航组件 拷贝vue-element-admin 的 @/layout/components/TagsView 目录及文件到 mengxuegu-blog-admin 对应目录下 [root@pgdb vue-element-admin]# cp -r src/layout/components/TagsView ../db-manager-system/src/layou…

UE蓝图基础学习笔记(未完待续2023/05/06)

文章目录 一、项目创建1&#xff09;准备流程&#xff08;选择模板、开发语言、平台、质量等&#xff09;2&#xff09;界面介绍 二、Actor三、操作关卡对象&#xff08;旋转、移动、缩放和坐标轴&#xff09;四、常用快捷键五、运行游戏六、蓝图介绍七、蓝图节点八、操作事件图…

Vben Admin 自学记录 —— Drawer组件的基本使用及练习(持续更新中...)

Drawer 抽屉组件 对 antv 的 drawer 组件进行封装&#xff0c;扩展拖拽&#xff0c;全屏&#xff0c;自适应高度等功能。 Drawer相关使用及概念 练习 —— 在之前table基础上&#xff0c;添加查看功能&#xff0c;点击查看按钮&#xff0c;弹出抽屉显示单条表格数据&#xf…

基于80C51单片机的电子钟设计与仿真

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87761539?spm1001.2014.3001.5503 源码获取 主要内容&#xff1a; 电子钟是一种利用数字电路来显示秒、分、时的计时装置&#xff0c;与传统的机械钟相比&#xf…

SpringBatch之实际操作

文章目录 1 SpringBatch操作1.1 SpringBatch介绍1.2 依赖配置相关1.2.1 pom.xml1.2.2 mysql 依赖库表1.2.3 启动配置1.2.4 数据库配置 1.3 示例Demo1.3.1 简单执行1.3.2 报错 1.4 流程控制1.4.1 多步骤任务1.4.2 Flow用法1.4.3 并发执行1.4.4 任务决策1.4.5 任务嵌套 1.5 数据操…

Illustrator如何使用图层与蒙版之实例演示?

文章目录 0.引言1.绘制可爱冰淇淋图标2.霓虹渐变立体文字海报3.炫彩花纹背景 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;…

电影推荐算法2

模型创建 title _ count, title _ set, genres2int, features, targets _ values, ratings, users, movies, data, movies _ orig, users _ orig pickle.load (open (‘preprocess.p’, mode ‘rb’)) 加载数据后定义神经网络的模型结构&#xff1a; 1&#xff09;定义参数…

u盘文件名乱码的恢复方法

文件名全部变乱码了怎么恢复&#xff1f;U盘数据恢复方法 电脑里的目录文件名乱码了&#xff0c;这是什么状况呢&#xff1f;好端端的电脑突然就成这个样子了&#xff0c;真是令人摸不着头脑&#xff0c;对于这样的状况&#xff0c;多半是文件类型引起的&#xff0c;那么接下来…

Python:Python进阶:Python整数与 Numpy的数据溢出

numpy数据溢出 1.python 3 的整数上限和 python 2 的整数上限1.1 python 2的整数范围1.2 python 3 的整数范围 2. numpy数值表示2.1 那么numpy支持的数据类型和 python有什么不同了2.2 如何解决整数溢出问题 总结 实验一&#xff1a;使用 numpy库来表示正数 import numpy as n…

redis(4)

1)使用StringTemplateRedis操作String类型 1)判断redis中是否拥有key所对应的值&#xff0c;如果有返回true&#xff0c;没有那么直接返回false redisTemplate.hasKey(key); 2)如果redis中有key那么直接取出key所对应的值 redisTemplate.opsForValue().get(key) 3)删除单个key值…

2023.05.07 学习周报

文章目录 摘要文献阅读1.题目2.现存问题和解决方法3.本文贡献及相关工作4.GRU5.模型5.1 SESSION-PARALLEL MINI-BATCHES5.2 SAMPLING ON THE OUTPUT5.3 RANKING LOSS 6.实验6.1 准备6.2 基线6.3 优化6.4 结果 7.结论 数学建模1.综合评价模型的一般步骤2.层次分析法3.主成分分析…

【Java】中的多线程线程锁

多线程 文章目录 多线程线程的创建和启动sleep()stop() 线程的休眠和中断线程的优先级线程的礼让和加入yield()stop() 线程锁和线程同步synchronized 关键字 死锁概念 wait & notify methodThreadLocal的使用定时器 Timer守护线程再谈集合类parallelStreamforEachOrdered()…

怎么将三张图片合成一张图片?

怎么将三张图片合成一张图片&#xff1f;遇到这个问题&#xff0c;我们其实有很多方法来处理。我们首当其冲想到的是其中最常见的&#xff0c;可以使用我们手机的APP来处理&#xff0c;比如某秀秀等。但是此方法比较适合于尺寸比较小的图片进行合并&#xff0c;如果图片比较大的…