第一次作业:深入源码分析进程模型(Linux 0.01)
一、前言
本文基于Linux0.01源码,分析Linux系统中的进程模型,具体包括进程的介绍、组织、转换、调度等。
Linux0.0.1源码下载地址:
https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/
二、进程介绍
1、进程的组成:程序段、相关数据段、进程控制块(PCB)(进程存在的唯一标志)。
2、进程的特征:动态性、并发行、独立性、异步性、结构性。
3、进程的基本功能:进程创建、进程运行、进程中止、进程调度
三、进程的组织
从系统内核的角度来看,一个进程仅仅是进程控制表中的一项。进程控制表中的每一项都是一个task_struct结构:
struct task_struct { /* these are hardcoded - don't touch */ long state; /* -1 unrunnable, 0 runnable, >0 stopped */ long counter; long priority; long signal; fn_ptr sig_restorer; fn_ptr sig_fn[32]; /* various fields */ int exit_code; unsigned long end_code,end_data,brk,start_stack; long pid,father,pgrp,session,leader; unsigned short uid,euid,suid; unsigned short gid,egid,sgid; long alarm; long utime,stime,cutime,cstime,start_time; unsigned short used_math; /* file system info */ int tty; /* -1 if no tty, so it must be signed */ unsigned short umask; struct m_inode * pwd; struct m_inode * root; unsigned long close_on_exec; struct file * filp[NR_OPEN]; /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */ struct desc_struct ldt[3]; /* tss for this task */ struct tss_struct tss; };
而进程控制表既是一个数组,又是一个双向链表,同时又是一个树。其物理实现是一个包括多个指针的静态数组。
系统启动后,内核通常作为一个进程的代表,一个指向task_struct的全局指针变量current用来记录正在运行的进程,它只能由进程调度改变。
四、进程状态的转换
在Linux0.01内核中的include/Linux/sched.h文件中,定义了进程的5种不同状态:
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1 #define TASK_UNINTERRUPTIBLE 2 #define TASK_ZOMBIE 3 #define TASK_STOPPED 4
其对应功能如表所示:
TASK_RUNNING |
表示进程在“Ready List”中,这个进程除了CPU以外,获得了所有的其他资源 |
TASK_INTERRUPTIBLE | 进程在睡眠中,正在等待一个信号或者一个资源(Sleeping) |
TASK_UNINTERRUPTIBLE | 进程等待一个资源,当前进程在“Wait Queue” |
TASK_ZOMBIE | 僵尸进程(没有父进程的子进程) |
TASK_STOPPED | 标识进程正在被调试 |
表中的进程各种状态切换关系的描述如下图所示:
五、进程的调度
处于 TASK RUNNING状态的进程移到运行队列( run queue),将由 schedule()函数CPU调度算法在合适的时候被选中运行,分配给CPU。调度程序做的工作大致是这样的:
调度函数选择另一个进程之前必须处理当前进程。
如果当前进程的调度策略是环则它放到运行队列的最后。
如果任务状态是TASK_ INTERRUPTIBLE并且它上次调度时收到过一个信号,它的状态变为TASK_ RUNNING;如果当前进程超时,它的状态成为TASK_RUNNING;如果当前进程的状态为 RUNNING,则保持此状态;不是 RUNNING或者 INTERRUPTIBLE的进程将被从运行队列中删除。这意味着当调度程序查找最值得运行的进程时不会考虑除 RUNNING和 INTERRUPTIBLE以外的进程。
调度程序查看运行队列中的进程,查找最适合运行于的进程。如果有实时的进程(具有实时调度策略),就会比普通进程更重一些。
如果最适合运行的进程不是当前进程,当前进程必须被挂起,从而运行新的进程。
schedule()函数:
void schedule(void) { int i,next,c; struct task_struct ** p; /* check alarm, wake up any interruptible tasks that have got a signal */ 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 && (*p)->state==TASK_INTERRUPTIBLE) (*p)->state=TASK_RUNNING; } /* this is the scheduler proper: */ while (1) { //此循环是取得任务的counter最高的那个任务,作为要切换的任务 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; //如果所有的任务counter都一样,且都为0,那么执行下面的代码,根据任务的先后顺序和权限大小重设各任务的counter for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) (*p)->counter = ((*p)->counter >> 1) + (*p)->priority; } switch_to(next); //切换任务 }
sched.c中实现进程的其他状态:
void sleep_on(struct task_struct **p) //进程休眠 { struct task_struct *tmp; if (!p) return; if (current == &(init_task.task)) //初始化任务不能休眠 panic("task[0] trying to sleep"); tmp = *p; *p = current; current->state = TASK_UNINTERRUPTIBLE; schedule(); if (tmp) tmp->state=0; //让**p的进程进入运行状态 } void interruptible_sleep_on(struct task_struct **p) { struct task_struct *tmp; if (!p) return; if (current == &(init_task.task)) panic("task[0] trying to sleep"); tmp=*p; *p=current; repeat: current->state = TASK_INTERRUPTIBLE; schedule(); if (*p && *p != current) { (**p).state=0; //先唤醒其他的进程 goto repeat; } *p=NULL; if (tmp) tmp->state=0; //唤醒指定的进程 } void wake_up(struct task_struct **p) { if (p && *p) { (**p).state=0; *p=NULL; } }
六、看法与感想
Linux 0.01的进程模型十分简单,在调度时也是十分精简,但是在Linux不断发展的过程中,这个最初的版本早已无法满足用户的需求。当下的调度算法已经比最原始的算法优化了太多,在公平的使用CUP的基础上还能考虑到不同进程的优先级,给与整个系统更加优质的用户体验。更新永远不会停止,要学习的东西还有很多。