linux系统的进程管理

简介: linux系统的进程管理

前言

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


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

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_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. 进程的创建是系统调用
.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 结构体:
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

目录
相关文章
|
5天前
|
IDE Linux 开发工具
Linux 系统上安装
在Linux和Mac上安装Lua 5.3.0只需下载源码,解压,编译和安装。Windows用户可选择SciTE IDE或通过LuaForWindows在Github或Google Code下载安装。创建 HelloWorld.lua,使用`lua HelloWorld.lua`运行显示&quot;Hello World!&quot;。另可参考LuaDist官方推荐方式安装。
|
5天前
|
消息中间件 算法 Linux
【Linux】详解如何利用共享内存实现进程间通信
【Linux】详解如何利用共享内存实现进程间通信
|
5天前
|
Linux
【Linux】命名管道的创建方法&&基于命名管道的两个进程通信的实现
【Linux】命名管道的创建方法&&基于命名管道的两个进程通信的实现
|
5天前
|
Linux
【Linux】匿名管道实现简单进程池
【Linux】匿名管道实现简单进程池
|
5天前
|
Linux
【Linux】进程通信之匿名管道通信
【Linux】进程通信之匿名管道通信
|
3天前
|
运维 Linux
CentOS系统openssh-9,你会的还只有初级Linux运维工程师的技术吗
CentOS系统openssh-9,你会的还只有初级Linux运维工程师的技术吗
|
4天前
|
监控 JavaScript Linux
Linux系统之部署Homepage个人导航页
【5月更文挑战第13天】Linux系统之部署Homepage个人导航页
24 1
|
5天前
|
监控 JavaScript 网络协议
Linux系统之安装uptime-kuma服务器监控面板
【5月更文挑战第12天】Linux系统之安装uptime-kuma服务器监控面板
18 0
|
5天前
|
Linux Perl
Linux系统的文本处理
Linux系统的文本处理
|
5天前
|
Linux API
Linux系统编程之文件编程常用API回顾和文件编程一般步骤
Linux系统编程之文件编程常用API回顾和文件编程一般步骤
Linux系统编程之文件编程常用API回顾和文件编程一般步骤