linux系统的进程管理

文章目录

  • 前言
    • 一、系统的进程的运转方式
      • 1、系统时间:(jiffies 系统滴答)
      • 2、task_struct
    • 二、如何创建一个新的进程(重要)
    • 三、进程调度
      • ①、主要函数
      • ②、辅助函数
    • 四、进程的退出
      • 内核的销毁


前言

本文讲解系统的进程管理相关内容,系统的进程管理是有关系统的所有进程的调度、排序、分配资源、创建、销毁等,是比较重要的内容。


一、系统的进程的运转方式

1、系统时间:(jiffies 系统滴答)

  • CPU 内部有一个 RTC,会在上电的时候调用 mktime 函数算出从 1970 年的 1 月 1 日 0 时开始到当前开机点所过的秒数,给 mktime 函数传来的时间结构体的赋值是由初始化时从 RTC(CMOS)读出的参数,转换为时间存入全局变量中,并且会为 JIFFIES 所用
  • JIFFIES 是一个系统的时钟滴答,一个系统滴答是 10ms,定时器
    • 10ms 一个系统滴答 ----> 每隔 10ms 会引发一个定时器中断(中断服务函数中进行了 JIFFIES 的自加,在 System_call.s 的 timer_interrupt 中进行自加)
  • 调用 do_timer 函数
    	if (cpl) // CPL 变量是内核中用来指示被中断程序的特权  0:内核进程 3:被中断的是用户进程
    		current->utime++;	// utime:用户程序的运行时间
    	else
    		current->stime++;	// stime:内核程序的运行时间
    
    • next_timer 是嫁接于 jiffies 变量的所有定时器的事件链表
    • current->counter -----> 进程的时间片
    • task_struct ----> 一个进程;task_struct[] ----> 进程向量表;每个进程都有一个 counter
      • counter 在哪里用?
        • 进程的调度就是 task_struct[] 进程链表的检索,找时间片最大的那个进程对象(task_struct),然后进行调用,直到时间片为 0,退出,之后再进行新一轮的调用
      • counter 在哪里被设置?
        • 当全部的 task_struct[] (task[])所有的进程 counter 都为 0,就进行新一轮的时间片分配
        • 优先级分配
          • (*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
      • 优先级时间片轮转调度算法

具体详细内容参考Linux内核完全注释:基于0.11内核(修正版V3.0).pdf P299~P300
链接:Linux内核完全注释:基于0.11内核(修正版V3.0).pdf
提取码:ygz8

2、task_struct

  • 进程的状态
  • 分时技术进行多进程调度
    一个进程中由以下几部分组成:
    在这里插入图片描述

二、如何创建一个新的进程(重要)

  • 进程是如何创建的?
    • Linux 在初始化的过程中会进行 0 号进程的创建,fork
    • main.c
      • sched.c—>sched_init—>gdt

      • linux系统级别 GDT
        在这里插入图片描述

    • sched_init(); 做了什么事情
  • 内核态:不可抢占
  • 用户态:可抢占
    • move_to_user_mode():把内核状态从内核态切换到用户态
  • 在内核初始化的过程中,会手动创建 0 号进程,0 号进程是所有进程的父进程
  • 进程的初始化
    • 在 0 号进程中:
        1. 打开标准输入、输出、错误的控制台句柄
        1. 创建 1 号进程,如果创建成功,则在 1 号进程中
        • 首先打开了 “/etc/rc” 文件
        • 执行 SHELL 程序 “/bin/sh”
        1. 0 号进程不可能结束,它会在没有其他进程调用的时候调用,只会执行 for(;;) pause();
  • 进程创建
    • fork
        1. 在 task 链表中找一个任务进程空位存放当前的进程
        1. 创建一个 task_struct
        1. 设置 task_struct
    • 进程的创建就是对 0 号进程或者当前进程的复制
      • 0 号进程复制就是结构体的赋值,把 task[0] 对应的 task_struct 复制给新创建的 task_struct
      • 对于堆栈的拷贝,当进程做创建的时候要保持复制原有的堆栈
      1. 进程的创建是系统调用
      .align 2
      _sys_fork:
      	call _find_empty_process
      	testl %eax,%eax
      	js 1f
      	push %gs
      	pushl %esi
      	pushl %edi
      	pushl %ebp
      	pushl %eax
      	call _copy_process
      	addl $20,%esp
      1:	ret
      
        1. 给当前要创建的进程分配一个进程号。find_empty_process
        1. 创建一个子进程的 task_struct 结构体:
        struct task_struct *p; 
        p = (struct task_struct *) get_free_page();
        
        1. 将当前的子进程放入到整体进程链表中
        task[nr] = p;
        
        1. 设置创建的 task_struct 结构体
        p->pid = last_pid;
        p->father = current->pid;
        p->counter = p->priority;
        p->signal = 0;
        p->alarm = 0;
        p->leader = 0;		/* process leadership doesn't inherit */
        p->utime = p->stime = 0;
        p->cutime = p->cstime = 0;
        p->start_time = jiffies;
        p->tss.back_link = 0;
        p->tss.esp0 = PAGE_SIZE + (long) p;
        p->tss.ss0 = 0x10;
        p->tss.eip = eip;
        p->tss.eflags = eflags;
        p->tss.eax = 0;
        p->tss.ecx = ecx;
        p->tss.edx = edx;
        p->tss.ebx = ebx;
        p->tss.esp = esp;
        p->tss.ebp = ebp;
        p->tss.esi = esi;
        p->tss.edi = edi;
        p->tss.es = es & 0xffff;
        p->tss.cs = cs & 0xffff;
        p->tss.ss = ss & 0xffff;
        p->tss.ds = ds & 0xffff;
        p->tss.fs = fs & 0xffff;
        p->tss.gs = gs & 0xffff;
        p->tss.ldt = _LDT(nr);
        p->tss.trace_bitmap = 0x80000000;
        
        如果当前进程使用了协处理器,那就设置当前创建进程的协处理器
        if (last_task_used_math == current)
        __asm__("clts ; fnsave %0"::"m" (p->tss.i387));
        
        进行老进程向新进程代码段、数据段(LDT)的拷贝
        int copy_mem(int nr,struct task_struct * p) 
        
        如果父进程打开了某个文件,那么子进程也同样打开这个文件,所以将文件的打开计数+1
        for (i=0; i<NR_OPEN;i++)
        	if (f=p->filp[i])
        		f->f_count++;
        
        继承父进程相关属性
        if (current->pwd)
        	current->pwd->i_count++;
        if (current->root)
        	current->root->i_count++;
        if (current->executable)
        	current->executable->i_count++;
        
        设置进程两个段,并且结合刚才拷贝过来的,组装成一个进程
        set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
        set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
        
        给程序的状态标志为可运行状态
        p->state = TASK_RUNNING;	/* do this last, just in case */
        
        返回新创建进程的 pid
        return last_pid;
        

具体详细内容参考Linux内核完全注释:基于0.11内核(修正版V3.0).pdf P242~P252P325~P333
链接:Linux内核完全注释:基于0.11内核(修正版V3.0).pdf
提取码:ygz8

进程被创建到了链表中,如何再进行进一步的调佣和调度?请看下面进程调度!!!

三、进程调度

①、主要函数

  • void schedule(void) -------> 进程调度函数
    • 进程状态
      • 运行状态 ------> 可以被运行,就绪状态,进程切换只有在运行状态
      • 可中断睡眠状态 ------> 可以被信号中断,使其变成 RUNNING
      • 不可中断睡眠状态 ------> 只能被 wakeup 所唤醒变为 RUNNING
      • 暂停状态 ------> 收到 SIGSTOP、SIGTSTP、SIGTTIN
      • 僵死状态 ------> 进程停止运行了,但是父进程还没有将其清空
        • #define TASK_RUNNING 0
        • #define TASK_INTERRUPTIBLE 1
        • #define TASK_UNINTERRUPTIBLE 2
        • #define TASK_ZOMBIE 3
        • #define TASK_STOPPED 4
    for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
    		if (*p) {
    			if ((*p)->alarm && (*p)->alarm < jiffies) {
    					(*p)->signal |= (1<<(SIGALRM-1));
    					(*p)->alarm = 0;
    				}
    			if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && //信号不为空并且去除掉不能引发进程就绪状态的阻塞信号
    			(*p)->state==TASK_INTERRUPTIBLE)
    				(*p)->state=TASK_RUNNING;
    		} // 如何该进程为可中断睡眠状态,则如果该进程有非屏蔽信号出现就将进程的状态设置为就绪状态
    while (1) {
    	c = -1;
    	next = 0;
    	i = NR_TASKS;
    	p = &task[NR_TASKS];
    	while (--i) {
    		if (!*--p)
    			continue;
    		if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
    			c = (*p)->counter, next = i;
    	}
    	if (c) break; // 如果 c 不为空,则当前进程链表中,还有一些进程的时间片没有用完。
    				  // 如果 c 为空,则所有进程的时间片都已经用完了
    	// 进行时间片的重分配
    	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
    		if (*p)
    			(*p)->counter = ((*p)->counter >> 1) + // 时间片的分配:counter = counter/2 + priority
    					(*p)->priority;
    }
    
  • switch_to() ------> 进程切换函数
    • 把进程切换为当前进程
      • 1)将需要切换的进程赋值给当前进程指针
      • 2)进行进程的上下文切换
        • 上下文:程序运行是 CPU 的特殊寄存器、通用寄存器(TSS)等信息+当前堆栈中的信息
  • void sleep_on(struct task_struct **p)
    • 当某个进程想要访问CPU的资源的时候,碰巧 CPU 资源被占用,那么就会调用SLEEPON 函数,把进程休眠
    	if (current == &(init_task.task))
    		panic("task[0] trying to sleep");
    

②、辅助函数

  • void show_task(int nr,struct task_struct * p) ------> 打印 p->pid,p->state 打印栈的空闲大小

具体详细内容参考Linux内核完全注释:基于0.11内核(修正版V3.0).pdf P281~P302
链接:Linux内核完全注释:基于0.11内核(修正版V3.0).pdf
提取码:ygz8

四、进程的退出

linux内核代码中以 syscall_、do_xxx 开头的基本上都是中断调用的函数

内核的销毁

  • exit 是销毁函数 ------> 一个系统调用 ------> do_exit
    • 首先该函数会释放进程的代码段和数据段占用的内存
    • 关闭进程打开的所有文件,对当前的目录和 i 节点进行同步(文件操作)
    • 如果当前要销毁的进程有子进程,那么就让 1 号进程作为新的父进程(init 进程)
    • 如果当前进程是一个会话头进程,则会终止会话中的所有进程
    • 改变当前进程的运行状态,变成 TASK_ZOMBIE 僵死状态,并且向其父进程发送 SIGCHLD
  • void release(struct task_struct * p)
    • 完成清空了任务描述表中的对应进程表项,释放对应的内存页(代码段 数据段 堆栈)
  • static inline int send_sig(long sig,struct task_struct * p,int priv)
    • 给指定的 p 进程发送对应的 sig 信号
  • static void kill_session(void)
    • 终止会话,终止当前进程的会话给其发送 SIGHUP
  • int sys_kill(int pid,int sig)
    • kill- 不是杀死的意思,向对应的进程号或者进程组号发送任何信号
      • pid
        • pid > 0,给对应的 pid 发送 sig
          else if (pid>0) while (--p > &FIRST_TASK) {
          		if (*p && (*p)->pid == pid) 
          			if (err=send_sig(sig,*p,0))
          				retval = err;
          	}
          
        • pid = 0,给当前进程的进程组发送 sig
          if (!pid) while (--p > &FIRST_TASK) {
          		if (*p && (*p)->pgrp == current->pid) 
          			if (err=send_sig(sig,*p,1))
          				retval = err;
          	}
          
        • pid = -1,给任何进程发送
          else if (pid == -1) while (--p > &FIRST_TASK)
          		if (err = send_sig(sig,*p,0))
          			retval = err;
          
        • pid < -1,给进程组号为 -pid 的进程组发送信号
          else while (--p > &FIRST_TASK)
          		if (*p && (*p)->pgrp == -pid)
          			if (err = send_sig(sig,*p,0))
          				retval = err;
          
  • static void tell_father(int pid)
    • 子进程向父进程发送SIGCHLD信号
  • int do_exit(long code)
    • current->pid 就是当前关闭的进程
    • 首先该函数会释放进程的代码段和数据段占用的内存
      free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
      	free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
      
    • 关闭进程打开的所有文件,对当前的目录和 i 节点进行同步(文件操作)
      for (i=0 ; i<NR_OPEN ; i++)
      		if (current->filp[i])
      			sys_close(i);
      	iput(current->pwd);
      	current->pwd=NULL;
      	iput(current->root);
      	current->root=NULL;
      	iput(current->executable);
      	current->executable=NULL;
      
    • 如果当前要销毁的进程有子进程,那么就让 1 号进程作为新的父进程(init 进程)
      for (i=0 ; i<NR_TASKS ; i++)
      		if (task[i] && task[i]->father == current->pid) {
      			task[i]->father = 1;
      			if (task[i]->state == TASK_ZOMBIE)
      				/* assumption task[1] is always init */
      				(void) send_sig(SIGCHLD, task[1], 1);
      		}
      
    • 如果使用了终端或者协处理器,那么将终端关闭 协处理器关闭
      if (current->leader && current->tty >= 0)
      		tty_table[current->tty].pgrp = 0;
      	if (last_task_used_math == current)
      		last_task_used_math = NULL; 
      
    • 如果当前进程是一个会话头进程,则会终止会话中的所有进程
      if (current->leader)
      		kill_session();
      
    • 改变当前进程的运行状态,变成TASK ZOMBIE僵死状态,并且向其父进程发送SIGCHLD信号
      current->state = TASK_ZOMBIE;
      tell_father(current->father);
      
    • 重新调度进程
      schedule(); 
      
  • int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
    • 父进程在运行子进程的时候一般都会运行 wait waitpid 这两个函数(父进程等待某个子进程终止),当父进程收到 SIGCHLD 信号时父进程会终止僵死状态的子进程
    • 首先父进程会把子进程的运行时间累加到自己的进程变量中
      current->cutime += (*p)->utime;
      current->cstime += (*p)->stime;
      
    • 把对应的子进程的进程描述结构体进行释放,置空任务数组中的空槽
      release(*p);
      

具体详细内容参考Linux内核完全注释:基于0.11内核(修正版V3.0).pdf P319~P325
链接:Linux内核完全注释:基于0.11内核(修正版V3.0).pdf
提取码:ygz8


我的qq:2442391036,欢迎交流!


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode142题:环形链表II(python3)

代码思路&#xff1a; 双指针的第一次相遇&#xff1a; 设两指针 fast&#xff0c;slow 指向链表头部 head 。 令 fast 每轮走 2 步&#xff0c;slow 每轮走 1 步。 fast 指针走过链表末端&#xff0c;说明链表无环&#xff0c;此时直接返回 null。 如果链表存在环&#xff0c;…

学习Java的第二天

如何使用文本文档在cmd里打印出HelloWorld 1、创建一个文本文档&#xff0c;并命名为HelloWorld&#xff0c;将后缀改为java&#xff08;需要自己去把后缀打开显示出来&#xff09; 2、打开编辑 也可以双击打开 3、在里面写出以下代码 上面红框里为你要打印的语句&#xff0c;…

Mybatis-Plus——05,乐观锁(新注解)

乐观锁&#xff08;新注解&#xff09; 一、数据库添加一个字段二、实体类添加version注解三、注册乐观锁插件四、测试一下4.1成功的乐观锁4.2失败的乐观锁————————创作不易&#xff0c;笔记不易&#xff0c;如觉不错&#xff0c;请三连&#xff0c;谢谢~~ 乐观锁实现方…

zabbix监控中间件服务

zabbix监控Nginx 自定义nginx访问量的监控项&#xff0c;首先要通过脚本将各种状态的值取出来&#xff0c;然后通过zabbix监控。找到自定义脚本上传到指定目录/etc/zabbix/script/ 在zbx-client客户端主机操作 #创建目录&#xff0c;然后将脚本上传到该目录mkdir /etc/zabbix/…

信息安全与阿里云等保三级方案实践总结

信息安全在当今数字化时代变得至关重要&#xff0c;企业和组织需要采取有效措施来保护其数据和信息资产。阿里云作为中国领先的云服务提供商&#xff0c;提供了等保三级方案&#xff0c;帮助用户满足国家信息安全等级保护的要求。本文将探讨信息安全和阿里云等保三级方案的重要…

使用vite创建一个vue3项目

创建一个vue3项目 1.使用命令npm create vuelatest来创建一个vue3项目&#xff0c;注意&#xff1a;官网说明了必须node版本是18及以上的&#xff0c;这边需要注意下 2.然后根据提示进入项目目录 先npm install安装依赖&#xff0c;然后npm run dev启动项目 大家可以看到&am…

TCP与UDP基础

思维导图&#xff1a; TCP&#xff1a; 服务器 #include<myhead.h> #define SER_IP "192.168.252.163" #define SER_PORT 6666 int main(int argc, const char *argv[]) {//&#xff11;、创建用于监听的套接字int sfd-1;sfdsocket(AF_INET,SOCK_STREAM,0);/…

2023 电脑PC FetchV网页视频下载器 浏览器插件

FetchV&#xff0c;它可以下载网页视频&#xff0c;下载速度快到离谱&#xff0c;非常好用&#xff01; FetchV:网页视频下载器(HLS|m3u8|mp4|blob) 下载和录制各种格式的在线网页视频&#xff0c;包括HLS、m3u8、blob、mp4、webm等各种类型的视频。 这是一个通用的网页视频…

保留数据的重装系统教程!(win11系统)

上车警告&#xff01;&#xff01;&#xff01; 本教程无需思考&#xff0c;跟着操作一步一步来就能完成系统的重装。原理是将C盘系统重装&#xff0c;其他盘符数据保存。适用于系统盘重装数据或更改系统版本。 重要提示&#xff01;&#xff01;&#xff01; C盘有重要学习资…

视频产品介绍:国标28181网关(GB/T28118网关)

目 录 一、概述 二、产品功能 &#xff08;一&#xff09;功能描述 &#xff08;二&#xff09;功能展示 1、国标接入 2、资源绑定 三、产品能力 &#xff08;一&#xff09;接入能力 &#xff08;二&#xff09;多级架构 四、特点优势 &#xff08;一&am…

用硬盘空间管理工具TreeSize拯救C盘容量

一、软件简介 TreeSize 是一款卓越的硬盘空间管理工具&#xff0c;能智能检测磁盘文件和存储量&#xff0c;为您提供详尽的磁盘空间信息&#xff0c;帮助您根据需求删除无用文件&#xff0c;释放更多空间。使用该工具可有效分析硬盘存储情况&#xff0c;找出大文件和未使用文件…

【React Native】修改Android端应用的图标

简言 修改android应用的图标的步骤&#xff0c;效果如下&#xff1a; 修改图标 Android应用的app图标在 /android/app/src/main/res 下&#xff1a; 准备图标 在替换之前要准备好图标各精度的图片。 图标工厂&#xff0c;这个网站可以快速免费生成图标文件供使用。 可以…

MySQL 空间碎片详解

文章目录 前言1. 空间碎片如何产生2. 空间碎片如何查看3. 空间碎片如何回收后记 前言 MySQL 数据库在运行过程中&#xff0c;随着时间的推移&#xff0c;可能会出现空间碎片的问题。空间碎片是指数据库表中不再使用的空间&#xff0c;但由于各种原因&#xff0c;这些空间并没有…

Linux 下安装 Git

Linux 下安装 Git 1 参考2 安装2.1 通过 yum方式安装&#xff08;不推荐&#xff09;2.2 通过源码编译安装&#xff08;推荐&#xff09; 3 配置SSH 1 参考 Linux 下安装 Git 2 安装 2.1 通过 yum方式安装&#xff08;不推荐&#xff09; 在Linux上安装git仅需一行命令即可…

[R] How to communicate with your data? - ggplot2

We have gone through the basic part of how to clean and process before analyzing your data. How to communicate with your data? R语言具有生成各种图形的多种可能性。 并非所有图形功能对初学者来说都是必要的。 复杂的图形需要长代码。 我们将从简单的图形元素开…

基于SSM的医院挂号系统

1 引言 1.1 课题背景及意义 社会发展迅速&#xff0c;以往的管理方式已经满足不了人们对获得信息的方式、方便快捷的需求。医院门诊挂号系统慢慢的被人们关注。网上获取信息十分的实时、便捷&#xff0c;只要系统在线状态&#xff0c;无论在哪里都能第一时间查找到理想的信息…

深度解析速卖通商品详情API:Python实战与高级技术探讨

速卖通商品详情API接口实战&#xff1a;Python代码示例 一、准备工作 在开始之前&#xff0c;请确保你已经完成了以下步骤&#xff1a; 在速卖通开放平台注册账号并创建应用&#xff0c;获取API密钥。阅读速卖通商品详情API接口的文档&#xff0c;了解接口的使用方法和参数要…

静态住宅代理IP如何选?这5点最重要

静态住宅代理IP&#xff0c;是一种在网络通信过程中提供固定IP地址的代理服务。与动态代理IP相比&#xff0c;静态代理IP提供的是持久且不变的IP地址。这种稳定性使得静态代理IP在需要长期稳定网络身份的场景中&#xff0c;如跨境电商/社媒养号、网络监控、品牌保护、长期数据爬…

人力资源管理软件大比拼:这篇文章帮你做出明智选择!

本期为您盘点的助力现代企业强力提效的人力资源管理软件有&#xff1a;Zoho People&#xff0c;Workday&#xff0c;BambooHR和Namely。 Zoho People人力资源管理软件 Zoho People是一款全面的云端人力资源管理&#xff08;HRM&#xff09;软件&#xff0c;由Zoho Corporation…

Android开发工程师面试题,2024年Android开发陷入饱和

前言 马上快到金三银四都春招阶段了&#xff0c;在这本就是跳槽、找工作的年后黄金时间&#xff0c;大多数求职者都早早做好年后求职的准备&#xff0c;其中不乏有年前早早辞了工作准备年后跳槽的有经验的职场老人们&#xff0c;也有一批即将毕业的应届毕业生的职场新人们。 …