前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >linux系统的进程管理

linux系统的进程管理

作者头像
Gnep@97
发布2024-03-07 08:58:21
1080
发布2024-03-07 08:58:21
举报

前言

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

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

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 函数

代码语言:javascript
复制
	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. 打开标准输入、输出、错误的控制台句柄
    2. 创建 1 号进程,如果创建成功,则在 1 号进程中
    • 首先打开了 “/etc/rc” 文件
    • 执行 SHELL 程序 “/bin/sh”
    1. 0 号进程不可能结束,它会在没有其他进程调用的时候调用,只会执行 for(;;) pause();

进程创建

fork

  1. 在 task 链表中找一个任务进程空位存放当前的进程
  2. 创建一个 task_struct
  3. 设置 task_struct

进程的创建就是对 0 号进程或者当前进程的复制

  • 0 号进程复制就是结构体的赋值,把 task[0] 对应的 task_struct 复制给新创建的 task_struct
  • 对于堆栈的拷贝,当进程做创建的时候要保持复制原有的堆栈
  1. 进程的创建是系统调用
代码语言:javascript
复制
.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
  2. 创建一个子进程的 task_struct 结构体:
代码语言:javascript
复制
struct task_struct *p; 
p = (struct task_struct *) get_free_page();
  1. 将当前的子进程放入到整体进程链表中
代码语言:javascript
复制
task[nr] = p;
  1. 设置创建的 task_struct 结构体
代码语言:javascript
复制
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
代码语言:javascript
复制
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 函数,把进程休眠
代码语言:javascript
复制
	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

代码语言:javascript
复制
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

代码语言:javascript
复制
if (!pid) while (--p > &FIRST_TASK) {
		if (*p && (*p)->pgrp == current->pid) 
			if (err=send_sig(sig,*p,1))
				retval = err;
	}

pid = -1,给任何进程发送

代码语言:javascript
复制
else if (pid == -1) while (--p > &FIRST_TASK)
		if (err = send_sig(sig,*p,0))
			retval = err;

pid < -1,给进程组号为 -pid 的进程组发送信号

代码语言:javascript
复制
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 就是当前关闭的进程

首先该函数会释放进程的代码段和数据段占用的内存

代码语言:javascript
复制
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
	free_page_tables(get_base(current->ldt[2]),get_limit(0x17));

关闭进程打开的所有文件,对当前的目录和 i 节点进行同步(文件操作)

代码语言:javascript
复制
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 进程)

代码语言:javascript
复制
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);
		}

如果使用了终端或者协处理器,那么将终端关闭 协处理器关闭

代码语言:javascript
复制
if (current->leader && current->tty >= 0)
		tty_table[current->tty].pgrp = 0;
	if (last_task_used_math == current)
		last_task_used_math = NULL; 

如果当前进程是一个会话头进程,则会终止会话中的所有进程

代码语言:javascript
复制
if (current->leader)
		kill_session();

改变当前进程的运行状态,变成TASK ZOMBIE僵死状态,并且向其父进程发送SIGCHLD信号

代码语言:javascript
复制
current->state = TASK_ZOMBIE;
tell_father(current->father);

重新调度进程

代码语言:javascript
复制
schedule(); 

int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)

父进程在运行子进程的时候一般都会运行 wait waitpid 这两个函数(父进程等待某个子进程终止),当父进程收到 SIGCHLD 信号时父进程会终止僵死状态的子进程

首先父进程会把子进程的运行时间累加到自己的进程变量中

代码语言:javascript
复制
current->cutime += (*p)->utime;
current->cstime += (*p)->stime;

把对应的子进程的进程描述结构体进行释放,置空任务数组中的空槽

代码语言:javascript
复制
release(*p);

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 一、系统的进程的运转方式
      • 1、系统时间:(jiffies 系统滴答)
      • 2、task_struct
    • 二、如何创建一个新的进程(重要)
      • 三、进程调度
        • ①、主要函数
        • ②、辅助函数
      • 四、进程的退出
        • 内核的销毁
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档