第一次作业:深入源码分析进程模型
写在最前:
- 本次使用的内核源码:Linux 0.12版本。
- 这篇文章在讲什么:旨在利用源码来帮助理解操作系统中进程这一概念。
进程:这是对正在运行程序的一个抽象。操作系统的其它所有内容都是围绕着进程的概念展开的。一个进程就是一个正在执行程序的实例。
一.操作系统是如何组织进程的
1.进程数
在sched.h中,有如下语句:
1 #define NR_TASKS 64
该语句定义了系统中的最多任务(进程)数,即:在同一瞬间,系统中最多可有64个进程。
2. 进程标识符
- 在sched.h中有一个进程的结构体:task_struct。在结构体中,定义了进程的标识符(唯一):
1 long pid;
- 注:任务0是“空闲”任务,当没有其他任务可以运行时会被调用。 它不能被杀死,它无法入睡。 任务[0]中的'状态'信息从不使用。
- 该结构体的完整定义如下:
1 struct task_struct {
2 /* these are hardcoded - don't touch */
3 long state; //任务的运行状态,-1 不可运行, 0 可运行(就绪), >0 已停止
4 long counter; //任务运行时间计数
5 long priority;
6 long signal;
7 struct sigaction sigaction[32];
8 long blocked; /* bitmap of masked signals */
9 /* various fields */
10 int exit_code;
11 unsigned long start_code,end_code,end_data,brk,start_stack;
12 long pid,pgrp,session,leader; //从左到右依次为:进程标识号(进程号)、进程组号、会话号、会话首领
13 int groups[NGROUPS]; //进程所属组号,一个进程可以属于多个组
14 /*
15 * pointers to parent process, youngest child, younger sibling,
16 * older sibling, respectively. (p->father can be replaced with
17 * p->p_pptr->pid)
18 */
19 //从左到右:指向父进程的指针、指向最新子进程的指针、指向比自己后创建的相邻进程的指针、指向比自己早创建的相邻进程的指针。
20 struct task_struct *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
21 unsigned short uid,euid,suid;
22 unsigned short gid,egid,sgid;
23 unsigned long timeout,alarm;
24 long utime,stime,cutime,cstime,start_time;
25 struct rlimit rlim[RLIM_NLIMITS];
26 unsigned int flags; /* per process flags, defined below */
27 unsigned short used_math;
28 /* file system info */
29 int tty; /* -1 if no tty, so it must be signed */
30 unsigned short umask;
31 struct m_inode * pwd;
32 struct m_inode * root;
33 struct m_inode * executable;
34 struct m_inode * library;
35 unsigned long close_on_exec;
36 struct file * filp[NR_OPEN];
37 /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
38 struct desc_struct ldt[3];
39 /* tss for this task */
40 struct tss_struct tss;
41 };
3. 进程可能的状态的定义
1 //这里定义了进程运行时可能处于的状态
2 #define TASK_RUNNING 0 //进程正在运行或已准备就绪
3 #define TASK_INTERRUPTIBLE 1 //进程处于可中断等待状态
4 #define TASK_UNINTERRUPTIBLE 2 //进程处于不可中断等待状态,主要用于I/O操作等待
5 #define TASK_ZOMBIE 3 //进程处于僵死状态,已经停止运行,但父进程还没发信号。
6 #define TASK_STOPPED 4 //进程已停止
二.进程状态如何转换(给出进程状态转换图)
-
进程的三个状态:
运行态:该时刻进程实际占有CPU。
就绪态:可运行,但因为其它进程正在运行而暂时停止。
阻塞态:除非某种外部时间发生,否则进程不能运行。
-
进程状态转换图:
-
转换关系图解释如下:
1. 进程因为等待输入而被阻止。
2. 调度程序选择另一个进程。
3. 调度程序选择这个进程。
4. 出现有效输入。
三.进程是如何调度的
调度:当系统中就绪的进程数大于系统中可用的CPU数时,可用的CPU必须选择下一个要运行的进程。在操作系统中,完成选择工作的这一部分称为调度程序(scheduler),该程序使用的算法称为调度算法(scheduling algorithm)。
在Linux 0.12中采用基于优先级排队的调度策略。
schedule()函数首先扫描任务数组。通过比较每个就绪态(TASK RUNNING) 任务的运行时间递减滴答计数counter 的值,来确定当前哪个进程运行的时间最少。哪一个的值大,就表示运行时间还不长,于是就选中该进程,并使用任务切换宏函数切换到该进程运行。
源码:
1 void schedule(void)
2 {
3 int i,next,c;
4 struct task_struct ** p; //任务结构指针的指针
5
6 /* check alarm, wake up any interruptible tasks that have got a signal */
7
8 for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
9 if (*p) {
10 if ((*p)->timeout && (*p)->timeout < jiffies) {
11 (*p)->timeout = 0;
12 if ((*p)->state == TASK_INTERRUPTIBLE)
13 (*p)->state = TASK_RUNNING;
14 }
15 if ((*p)->alarm && (*p)->alarm < jiffies) {
16 (*p)->signal |= (1<<(SIGALRM-1));
17 (*p)->alarm = 0;
18 }
19 if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
20 (*p)->state==TASK_INTERRUPTIBLE)
21 (*p)->state=TASK_RUNNING;
22 }
23
24 /* this is the scheduler proper: */
25 //-------------------这是调度程序的主要部分-------------------
26 while (1) {
27 c = -1;
28 next = 0;
29 i = NR_TASKS;
30 p = &task[NR_TASKS];
31 while (--i) {
32 if (!*--p)
33 continue;
34 if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
35 c = (*p)->counter, next = i;
36 }
37 if (c) break;
38 for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
39 if (*p)
40 (*p)->counter = ((*p)->counter >> 1) +
41 (*p)->priority;
42 }
43 switch_to(next);
44 }
四.谈谈自己对该操作系统进程模型的看法
对于Linux0.12 来讲,系统最多可有64 个进程同时存在。程序使用进程标识号(pid) 来标识每个进程。对于只有一个CPU 的系统,在某一瞬间只能有一个进程真正在运行。内核通过调度程序分时调度各个进程运行。 利用分时技术,在Linux 操作系统上可以(伪)同时运行多个进程。分时技术的基本原理是把CPU 的运行时间划分成个个规定长度的时间片(time slice),让每个进程在一个时间片内运行。当进程的时间片用完时系统就利用调度程序切换到另一个进程去运行。因此实际上对于具有单个CPU 的机器来说某一时刻只能运行一个进程。但由于每个进程运行的时间片很短(例如15 个系统滴答=l50ms),所以表面看来好像所有进程在同时运行着。
调度的实现有很多不同的算法,Linux 0.12版本较为早期了,后期的版本代码量似乎大了很多,有关进程调度的也一定更为完善。
调度的实现有很多不同的算法,Linux 0.12版本较为早期了,后期的版本代码量似乎大了很多,有关进程调度的也一定更为完善。
最后:
参考资料:
- Linux内核完全剖析-基于0.12内核 (赵炯) 中文pdf扫描版 下载地址:http://www.jb51.net/books/415345.html ;
- 《现代操作系统》第四版。