第一次作业:深入源码分析进程模型(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

其对应功能如表所示:

Linux 0.01中的进程状态
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的基础上还能考虑到不同进程的优先级,给与整个系统更加优质的用户体验。更新永远不会停止,要学习的东西还有很多。

posted @ 2018-05-01 13:45  西瓜郎太文  阅读(518)  评论(0编辑  收藏  举报