linux0.11中的进程相关总结

建议阅读这里列举的参考资料,本文仅用于备忘。

参考资料

本次的分享是非常浅的,更多内容请查看:

具体的请阅读《Linux内核0.11完全注释_V3.0 by 赵炯 (z-lib.org).pdf》

以及inter的官方英文手册: 《i386.pdf》

要想理解机器怎么工作的,必须要读一遍汇编:《汇编语言(第3版) 》王爽著
调汇编可以用vscode提供的插件,具体请参考这3篇文章:
https://blog.csdn.net/weixin_45708190/article/details/121134296
https://marketplace.visualstudio.com/items?itemName=xsro.masm-tasm
https://gitee.com/dosasm/masm-tasm/

关于操作系统,我个人推荐去大家看两个老师的视频,B站上面都有。
一个是 操作系统哈尔滨工业大学李治军老师 , 这个是真正将linux0.11源码的,需要汇编。
另外一个是南京大学jyy老师 , 这个课程,你只需要有C语言基础就可以。

什么是CPU ?

jyy老师的课程要求实现一个cpu,按照那个实验要求去实现一个就能明白,下面简单说明一下。

简单来说,CPU只会取指令,执行指令。
从微观的角度来看,程序就是状态机。

我们在通讯协议的时候,经常用状态机,比如在OTA中,我们定义9007这种指令会让我们跳转到升级这种状态。

而在CPU中,我们定义当收到某种101010组合时,应该执行什么样的状态切换。

比如,CPU 中寄存器 ax ,bx 分别为 1,0 ,这是一种状态。 我们把这种状态叫做 (1,0)

在执行: mov ax,bx 后, ax,bx 都为 1 , 这是另外一种状态。我们把这种状态叫做 (1,1)

CPU因为执行了 程序 mov ax,bx ,由状态(1,0) -> 状态(1,1)

我们把OTA用的协议是家电通讯协议,CPU使用的协议是x86指令集,riscv指令集, arm指令集。

什么是进程

进程是运行中的程序。

是系统进行资源分配和调度的基本单位。

裸机也可以跑,批处理也可以跑,为什么要有操作系统呢?

因为我们希望 计算机能同时处理多个任务(看上去) ,更合理的去利用CPU资源。

多任务并发执行,而CPU只有一个,这种矛盾 , 操作系统是怎么处理的?

下面,我们针对linux0.11来简单说一下。

如何描述一个进程

请添加图片描述
请添加图片描述

GDT是全局表,GDT中有 LDT和TSS。

一个进程由两部分构成:任务执行空间(由LDT指定),任务状态段TSS

当进行任务切换的时候,我们切换到内核,把LDT,TSS换成新任务的LDT,TSS就可以了,Linux中就是这么做的。

我们回到CPU视角,如果从状态机的视角来看,是不是相当于把旧任务的状态拍张照片存起来, 然后把新任务之前拍的照片载入回来?

任务切换就是 从 旧任务的状态X ,完全切换到 新任务的状态Y。

这只是操作系统提供的抽象,CPU实际上是不管的,它还是给什么指令就执行什么指令。

一些细节

段页结合的内存管理

我们程序是分段的,比如编译出来的程序,分 代码段,数据段,堆栈段

我们希望我们的程序就像我们写的代码那样一段一段的在内存中,

但是这样就不能充分利用我们的内存了,比如任务A分配了一大段内存,但是实际上它目前还没有用,而其他任务不能再申请这段内存了。这是很不划算的。

也就是说,我们物理内存,希望内存是一页一页的,1页4KB,那么这样最多浪费4KB。

所以我们引入了虚拟内存。

我们通过逻辑地址转换为虚拟地址,再转换到真正的物理地址。有了这种办法,我们还可以实现写时复制,以及硬盘内存交换。

而这种转换是由硬件完成的。

详细请查看《Linux内核0.11完全注释_V3.0 by 赵炯 (z-lib.org).pdf》第四章

什么是系统调用

可以去看李治军老师的课程,实现一个系统调用就能明白。

其实就是把函数写到 IDT表中,

然后CPU中有一条指令 INT , 这条指令会根据你传入参数 在 IDT表中 找到对应的 函数去执行

比如:执行 INT 0X80 就会调用system_call ,向eax中填入对应的功能号,就能跳到sys_call_table中的对应号码的函数。

用户程序要用硬件资源,都是要经过操作系统的,包括一个简单的printf(其实也不是很简单)。

走查一遍linux0.11中的系统调用代码。

创建任务0,任务1

这两个任务的开始,都是通过main函数的。
我们看main函数中关于这两个任务的操作。
void sched_init(void) 手动填好了task0的内容
在这里插入图片描述

void sched_init(void)
{
    
    
    int i;
    struct desc_struct * p; //描述符表结构指针。
 
    if (sizeof(struct sigaction) != 16) //检查内核代码是否有人修改。
        panic("Struct sigaction MUST be 16 bytes");
     
    //在全局描述符表中设置初始任务,
    set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));//gdt[4]
    set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));//gdt[5]
    p = gdt+2+FIRST_TSS_ENTRY; //gdt[6]
    for(i=1;i<NR_TASKS;i++) {
    
    //清除任务数组和描述符表项。
        task[i] = NULL;
        p->a=p->b=0;
        p++;
        p->a=p->b=0;
        p++;
    }
/* Clear NT, so that we won't have troubles with that later on */
 
//NT标志用于控制程序的递归调用 nested task , 当NT置位,当前中断执行iret指令时就会发生任务切换,NT指出TSS中的back_link字段是否有效。
    __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
    ltr(0);//加载到任务寄存器tr
    lldt(0);//加载到局部描述符表寄存器。
    outb_p(0x36,0x43);      /* binary, mode 3, LSB/MSB, ch 0 */  //初始化8253定时器。
    outb_p(LATCH & 0xff , 0x40);    /* LSB */
    outb(LATCH >> 8 , 0x40);  /* MSB */ //10ms发一次中断。
    set_intr_gate(0x20,&timer_interrupt);
    outb(inb_p(0x21)&~0x01,0x21); //开启时钟中断。
    set_system_gate(0x80,&system_call);
}

每个任务在GDT表中占有两个描述符选项:任务状态段TSS的描述符tss和局部描述符表LDT的描述符ldt

任务0完成配置之后的GDT表的样子。

在这里插入图片描述
此时,任务0的 tss,LDT ,以及这两者的选择符跟GDT表已经关联起来,剩下的,只要发生iret就能正常的进行任务切换,由于一开始并没task在跑,所以我们要任务的模拟一次iret.

让程序特权级变成用户模式3.

#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \ //保存堆栈到eax寄存器,
    "pushl $0x17\n\t" \  //首先将堆栈段选择符SS 入栈, 0x17任务数据段
    "pushl %%eax\n\t" \ //esp入栈
    "pushfl\n\t" \ //将eflage入栈
    "pushl $0x0f\n\t" \ //将task0代码段选择符CS入栈  , 0x0f任务代码段。
    "pushl $1f\n\t" \ //将标号1的偏移地址eip压入,
    "iret\n" \ //执行中断返回。
    "1:\tmovl $0x17,%%eax\n\t" \ //此时开始执行任务0
    "movw %%ax,%%ds\n\t" \ //初始化段寄存器指向本局部表的数据段。
    "movw %%ax,%%es\n\t" \
    "movw %%ax,%%fs\n\t" \
    "movw %%ax,%%gs" \
    :::"ax")

其中0x17是进程0的局部描述符表中数据段的选择符,0x0f是进程0的局部描述符表中代码段的选择符
在这里插入图片描述

当发生iret之后,ss,esp,eflags,cs,eip 都会被弹出,赋值到对应的寄存器,此时代码段跟数据段已经从内核切换到了进程0

然后把其他段也更新为0x17

然后立刻fork.
在这里插入图片描述
nr是任务号,是任务数组task中的索引,值得注意的是,除了进程0,其他任务号,跟这个任务什么时候启动是没有关系的。

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
        long ebx,long ecx,long edx,
        long fs,long es,long ds,
        long eip,long cs,long eflags,long esp,long ss) //系统调用fork用到。
{
    
    
    struct task_struct *p;
    int i;
    struct file *f;
 
    p = (struct task_struct *) get_free_page(); //指向一个真实的物理地址页面
    if (!p)
        return -EAGAIN;
    task[nr] = p; //task 指向这个进程的控制块
    *p = *current;  /* NOTE! this doesn't copy the supervisor stack */
    p->state = TASK_UNINTERRUPTIBLE; //防止中断了。
    p->pid = last_pid; //由 find_empty_process 找到。
    p->father = current->pid;
    p->counter = p->priority; //运行时间片等于优先级一般为15
    p->signal = 0;
    p->alarm = 0;
    p->leader = 0;       /* process leadership doesn't inherit */
    p->utime = p->stime = 0; //用户态与核心态运行时间都为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;//fork返回时,新进程会返回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); //任务局部表描述符的选择符,把GDT中本任务LDT段描述符的选择符保存到本任务的TSS段中。
    p->tss.trace_bitmap = 0x80000000;//IO位图基地址。高16位有效
    if (last_task_used_math == current)
        __asm__("clts ; fnsave %0"::"m" (p->tss.i387));//如果协处理器之前调用了,就把这个也保存到当前进程中,因为这是fork之前发生的。
    if (copy_mem(nr,p)) {
    
    
        task[nr] = NULL;
        free_page((long) p);
        return -EAGAIN;
    }
    for (i=0; i<NR_OPEN;i++) //如果父进程中由文件时打开的,对应文件打开次数+1
        if (f=p->filp[i])
            f->f_count++;
    if (current->pwd) //pwd,root,executable的引用次数+1
        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));//gdt+(nr<<1)+FIRST_TSS_ENTRY ,任务NR的tss描述符项,在全局表中的地址。每个任务占用GDT表中的2项
    set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));//将这个地址写到全局描述符中。
    p->state = TASK_RUNNING; /* do this last, just in case */ //将新任务设置为就绪态。
    return last_pid; //这个地方就是父进程的返回值,这也是为什么父进程中fork的返回值刚好是子进程的pid
}

拿到新的页,来建PCB, 同时把父进程的内存页面,打开的文件,pwd,root,executable等等的引用+1,需要操作时再触发中断,实现写时复制。

这个时候,0号进程的子进程,就建立好了。

这个函数,其实只是帮进程1向 系统注册了一些表。

CPU继续运行 进程0,然后进程0 ,一直调pause,这个函数是让内核去重新调度,找其他进程。

for(;;) pause();//没有其他任务执行,就来这里。父进程就一直暂停,暂时停止,其实是重新调度,任务0没有别的事。

那么这个时候,task表中已经有了子进程了,重新调度之后,子进程就进入了调度:


if (!fork()) {
    
          /* we count on this going ok */
    init();//在新建的子进程,任务1中执行。 fork 子进程返回0
}

切换到进程1 之后, 我们就把刚才设置好的现场恢复,eax刚好为0 ,进入init()。

在linux中,我们是通过PCB来描述一个进程的,TSS,LDT 就是我们非常关键的数据结构。

当线程切换的时候,就把TSS中的内容扣入CPU,将LDT表切换,即可。

也就是说,把当前进程的运行到的状态记录下来,把当前进程的代码,数据在内存中的位置记录下来。

切换任务之前保存好,切换任何的时候再把这些东西恢复。

同时,在内核中维护几张表,来保存这些内容的索引即可。

跟踪进程运行轨迹

pid X time
其中:- pid是进程的ID

X可以是N,J,R,W和E中的任意一个

N 进程新建 ,new

J 进入就绪态 , J

R 进入运行态 , run

W 进入阻塞态, wait

E 退出 ,exit

time表示X发生的时间。这个时间不是物理时间,而是系统的滴答时间(tick)

仔细阅读sched.c文件

本实验主要分析整个的调度流程,主要方法是,当发生状态转换时,进程的state变化时,我们把这个记录记下来。

先看看打印函数:

static char logbuf[1024];int fprintk(int fd, const char *fmt, ...)
{
    
    
    va_list args;
    int count;
    struct file *file;
    struct m_inode *inode;va_start(args, fmt);
    count = vsprintf(logbuf, fmt, args);
    va_end(args);if (fd < 3)     /* 如果输出到stdout或stderr,直接调用sys_write即可 */
    {
    
    
        __asm__("push %%fs\n\t"
            "push %%ds\n\t"
            "pop %%fs\n\t"
            "pushl %0\n\t"
            "pushl $logbuf\n\t"
            "pushl %1\n\t"
            "call sys_write\n\t"
            "addl $8, %%esp\n\t"
            "popl %0\n\t"
            "pop %%fs"
            ::"r" (count), "r" (fd)
            :"ax", "cx", "dx");
    }
    else    /* 假定>=3的描述符都与文件关联。事实上,还存在很多其他情况,这里并没有考虑 */
    {
    
    
        if (!(file=task[0]->filp[fd]))      /* 从进程0的文件描述符表中得到文件句柄 */
            return 0;
        inode = file->f_inode;__asm__("push %%fs\n\t"
            "push %%ds\n\t"
            "pop %%fs\n\t"
            "pushl %0\n\t"
            "pushl $logbuf\n\t"
            "pushl %1\n\t"
            "pushl %2\n\t"
            "call file_write\n\t"  //使用file_write,往文件/var/process.log中写入。
            "addl $12, %%esp\n\t"
            "popl %0\n\t"
            "pop %%fs"
            ::"r" (count), "r" (file), "r" (inode)
            :"ax", "cx", "dx");
    }
    return count;
}


linux源码中,只有这几种状态:

#define TASK_RUNNING        0  //正在运行或者已就绪
#define TASK_INTERRUPTIBLE  1  //进程处于可中断等待状态
#define TASK_UNINTERRUPTIBLE    2 //程序处于不可中断等待状态,主要用于I/O操作等待。
#define TASK_ZOMBIE     3   //程序处于僵死状态,已经停止运行,但父进程还没发信号。
#define TASK_STOPPED        4 //程序已停止。






N 进程新建 ,new
新进程在fork的时候产生,fork的关键是copy_process

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
        long ebx,long ecx,long edx,
        long fs,long es,long ds,
        long eip,long cs,long eflags,long esp,long ss)
{
    
    
    ...
    p->start_time = jiffies;fprintk(3, "%ld\t%c\t%ld\n", last_pid, 'N', jiffies); //新建
    ...
    p->state = TASK_RUNNING;    /* do this last, just in case */
    
    fprintk(3, "%ld\t%c\t%ld\n", last_pid, 'J', jiffies); //完成后,立马进入就绪态。return last_pid;
}


W 进入阻塞态, wait


//系统调用waitpid(),挂起当前进程,直到pid指定的子进程退出。
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
    
    
    ...
    if (flag) {
    
    
        if (options & WNOHANG)
            return 0;
        current->state=TASK_INTERRUPTIBLE;fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);//系统调用,等待。schedule();
        if (!(current->signal &= ~(1<<(SIGCHLD-1))))
            goto repeat;
        else
            return -EINTR;
    }
    return -ECHILD;
}


E 退出 ,exit


int do_exit(long code)
{
    
    
    ...
    current->state = TASK_ZOMBIE;
    fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'E', jiffies);
    /* print message end in 3-processTack */
    current->exit_code = code;
    tell_father(current->father);
    schedule();
    return (-1);    /* just to suppress warnings */
}




就绪与运行
void schedule(void)

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));//到点,给信号alarm
                    (*p)->alarm = 0;
                }
            if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && //除去被block掉的信号,还有其他信号,
            (*p)->state==TASK_INTERRUPTIBLE) //且进程是可中断的
                {
    
    
                    (*p)->state=TASK_RUNNING;
                    fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);//进程进入就绪。
                }
        }/* this is the scheduler proper: */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;//检查所有任务中,最需要被调度的进程,将这个号码放入next,这个就是下次要调度的进程。
        }
        if (c) break;   /* 找到一个counter不等于0 且是TASK_RUNNING状态中的counter最大的进程;或者当前系统没有一个可以运行的进程,此时c=-1, next=0,进程0得到调度,所以调度算法是不在意进程0的状态是不是TASK_RUNNING,这就意味这进程0可以直接从睡眠切换到运行! */
        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
            if (*p)
                (*p)->counter = ((*p)->counter >> 1) +
                        (*p)->priority;
    }
    if (task[next]->pid != current->pid) {
    
    //打印就绪,运行,就切到那个进程。
        if (current->state == TASK_RUNNING) {
    
    
            fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'J', jiffies);
        }
    
        fprintk(3, "%ld\t%c\t%ld\n", task[next]->pid, 'R', jiffies);} 
    switch_to(next);
}








int sys_pause(void)

/*
 * 系统无事可做的时候,进程0会不停地调用sys_pause(),以激活调度算法。此时它的状态可以是等待态,
 * 等待有其他可运行的进程;也可以是运行态,因为它是唯一一个在CPU上运行的进程,只不过运行的效果是等待。
 */
int sys_pause(void)
{
    
    
    current->state = TASK_INTERRUPTIBLE;
    if (current->pid != 0) //进程0死循环,一直调用这个函数。防止一直打印。
    {
    
    
        fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
    } 
    schedule();
    return 0;
}


void sleep_on(struct task_struct **p)

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插入“等待队列”头部,tmp是原来的头部 */
    current->state = TASK_UNINTERRUPTIBLE;
    fprintk(3, "%ld\t%c\t%ld\n", current->pid, 'W', jiffies);
    schedule();
    if (tmp) {
    
    
        fprintk(3, "%ld\t%c\t%ld\n", tmp->pid, 'J', jiffies);
        /* print message end in 3-processTack */
        tmp->state=TASK_RUNNING;    /* 唤醒队列中的上一个(tmp)睡眠进程。0换作TASK_RUNNING更好。在记录进程被唤醒时一定要考虑到这种情况,实验者一定要注意!!! */
    }
}


void interruptible_sleep_on(struct task_struct **p)

/*
 * TASK_UNINTERRUPTIBLE和TASK_INTERRUPTIBLE的区别在于不可中断的睡眠只能由wake_up()显示唤醒,
 * 再由上面的 schedule()语句后面的
 *
 * if (tmp) tmp->state=0;
 *
 * 依次唤醒,所以不可中断的睡眠一定是严格按照从“队列”(一个依靠放在进程内核栈中的指针变量tmp
 * 维护的队列)的首部进行唤醒。而对于可中断的进程,除了用wake_up唤醒以外,也可以用信号(给进程
 * 发送一个信号,实际上就是将进程PCB中维护的一个向量的某一位置位,进程需要在合适的时候处理
 * 这一位。感兴趣的实验者可以阅读有关代码)来唤醒,如在 schedule()中:
 *
 * for(p = &LAST_TASK; p > &FRIST_TASK; --p)
 *      if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
 *          (*p)->state==TASK_INTERRUPTIBLE)
 *          (*p)->state=TASK_RUNNING;//唤醒
 *
 * 就是当进程是可中断睡眠时,如果遇到一些信号就将其唤醒。这样的唤醒会出现一个问题,那就是可能会
 * 唤醒等待队列中间的某个进程,此时这个链就需要进行适当调整。interruptible_sleep_on和sleep_on
 * 函数的主要区别就在这里。
 */
void interruptible_sleep_on(struct task_struct **p) //将当前任务置为可中断的等待状态,并放入*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) {
    
    //此current不等于当时的current,说明队列中间有任务被唤醒了。
        (**p).state=TASK_RUNNING;//将队头唤醒。
        fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);
        goto repeat;//重新调度。
    }
    *p=NULL;
    if (tmp)//从队头开始,逐个释放。
        tmp->state=0;
        fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);
}






void wake_up(struct task_struct **p)

void wake_up(struct task_struct **p)
{
    
    
    if (p && *p) {
    
    
        (**p).state=0;
        fprintk(3, "%ld\t%c\t%ld\n", (*p)->pid, 'J', jiffies);
        *p=NULL;
    }
}




进程运行log分析
1   N   48  copy_process    //进程1新建(init())。此前是进程0建立和运行,我们是在move_to_user_mode后才新建/var/process.log文件进行进程运行轨迹记录的,所以进程0的新建和运行没有出现在log文件里
1   J   48  copy_process    //新建后进入就绪队列
0   J   48  schedule        /* --- 进程0从运行->就绪,让出CPU。(执行的是pause,调用)
1   R   48  schedule                进程1运行 --- */
2   N   49  copy_process    /* --- 进程1建立进程2。
2   J   49  copy_process            进程2会运行/etc/rc脚本,然后退出 --- */
1   W   49  sys_waitpid     //进程1开始等待(等待进程2退出)
2   R   49  schedule        //进程2运行
3   N   64  copy_process    /* --- 进程2建立进程3
3   J   64  copy_process            进程3是/bin/sh建立的运行脚本的子进程 --- */
2   E   68  do_exit         //进程2不等进程3退出,就先走一步了
1   J   68  schedule        /* --- 进程1此前在等待进程2退出,被阻塞。进程2退出后,重新进入就绪队列
1   R   68  schedule                进程1运行 --- */
4   N   69  copy_process    /* --- 进程1建立进程4,即shell
4   J   69  copy_process            进程4进入就绪队列 --- */
1   W   69  sys_waitpid     //进程1等待shell退出(除非执行exit命令,否则shell不会退出)
3   R   69  schedule        //进程3开始运行
3   W   75  sys_pause       //进程3主动睡觉,让出CPU
4   R   75  schedule        //进程4开始运行
5   N   107 copy_process    /* --- 进程5是shell建立的不知道做什么的进程(可能是ls命令?)
5   J   107 copy_process            进程5新建后进入就绪队列 --- */
4   W   108 sleep_on        //进程4进入不可中断睡眠状态
5   R   108 schedule        //进程5开始运行
4   J   110 wake_up         //进程5唤醒睡觉的进程4,进程4进入就绪队列
5   E   110 do_exit         //进程5很快退出
4   R   110 schedule        //进程4执行
4   W   115 interruptible_sleep_on  //shell等待用户输入命令
0   R   115 schedule        //因为无事可做,所以进程0重出江湖
4   J   902 wake_up         //用户开始输入命令了,唤醒了shell(从等待用户输入到用户开始输入命令总共经历了将近8s)
4   R   902 schedule        //进程4执行
4   W   903 interruptible_sleep_on  //shell等待用户输入命令
0   R   903 schedule        //因为无事可做,进程0再次执行
4   J   953 wake_up         //用户接着输入命令,唤醒shell(shell等待了0.5s)
4   R   953 schedule        //进程4执行
4   W   953 interruptible_sleep_on  //shell等待用户输入命令
0   R   953 schedule        //因为无事可做,进程0再次执行
4   J   1003    wake_up     //用户接着输入命令,唤醒shell(shell等待了0.5s)
4   R   1003    schedule    //shell执行
4   W   1003    interruptible_sleep_on  //shell等待用户输入
0   R   1003    schedule    //因为无事可做,进程0再次执行
4   J   1056    wake_up     //用户接着输入命令,唤醒shell(shell等待了0.53s)
4   R   1056    schedule    //shell执行
4   W   1056    interruptible_sleep_on  //shell等待用户输入
0   R   1056    schedule    //因为无事可做,进程0再次执行
4   J   1182    wake_up     //用户接着输入命令,唤醒shell(shell等待了1.26s),该命令输入完毕(用户输入该命令总共经历了将近1.8s)
4   R   1182    schedule    //shell执行
6   N   1184    copy_process    /* --- 进程6是shell建立的不知道做什么的进程
6   J   1185    copy_process            进程6进入就绪队列 --- */
4   W   1185    sys_waitpid     //shell开始等待(等待进程6退出)
6   R   1185    schedule        //进程6开始执行
6   E   1191    do_exit         //进程6很快退出
4   J   1191    schedule        //shell此前在等待进程6退出,被阻塞。进程6退出后,重新进入就绪队列
4   R   1191    schedule        //shell执行
7   N   1192    copy_process    /* --- 进程7是shell建立的不知道做什么的进程
7   J   1192    copy_process            进程7进入就绪队列 --- */
4   J   1193    schedule        /* --- shell从运行->就绪,让出CPU。
7   R   1193    schedule                进程7开始运行 --- */
7   E   1195    do_exit         //进程7很快退出
4   R   1195    schedule        //shell执行
4   W   1196    interruptible_sleep_on  //shell等待用户输入命令
0   R   1196    schedule        //因为无事可做,进程0再次执行
4   J   1251    wake_up         //用户开始输入命令,唤醒了shell(从等待用户输入命令到用户开始输入命令,总共经历了0.55s)
4   R   1251    schedule        //shell执行
4   W   1251    interruptible_sleep_on  //shell等待用户继续输入命令
0   R   1252    schedule        //因为无事可做,进程0再次执行
4   J   2345    wake_up         //用户接着输入命令,唤醒shell(shell等待了0.93s)
4   R   2345    schedule        //shell执行
4   W   2345    interruptible_sleep_on  //shell等待用户输入
0   R   2345    schedule        //因为无事可做,进程0再次执行
4   J   2386    wake_up         //用户接着输入命令,唤醒shell(shell等待了0.41s)
4   R   2386    schedule        //shell执行
4   W   2386    interruptible_sleep_on  //shell等待用户输入
0   R   2386    schedule        //因为无事可做,进程0再次执行
4   J   2823    wake_up         //用户接着输入命令,唤醒shell(shell等待了4.37s)
4   R   2823    schedule        //shell执行
4   W   2823    interruptible_sleep_on  //shell等待用户输入
0   R   2823    schedule        //因为无事可做,进程0再次执行
4   J   2864    wake_up         //用户接着输入命令,唤醒shell(shell等待了0.41s)
4   R   2864    schedule        //shell执行
4   W   2864    interruptible_sleep_on  //shell等待用户输入
0   R   2864    schedule        //因为无事可做,进程0再次执行
4   J   2898    wake_up         //用户接着输入命令,唤醒shell(shell等待了0.34s)
4   R   2898    schedule        //shell执行
4   W   2898    interruptible_sleep_on  //shell等待用户继续输入
0   R   2898    schedule        //因为无事可做,进程0再次执行
4   J   2936    wake_up         //用户接着输入命令,唤醒shell(shell等待了0.38s)
4   R   2936    schedule        //shell执行
4   W   2936    interruptible_sleep_on  //shell等待用户输入
0   R   2936    schedule        //因为无事可做,进程0再次执行
4   J   3046    wake_up         //用户接着输入命令,唤醒shell(shell等待了1.1s)
4   R   3046    schedule        //shell执行
4   W   3046    interruptible_sleep_on  //shell等待用户输入
0   R   3046    schedule        //因为无事可做,进程0再次执行
3   J   3074    schedule        /* --- 进程3从睡眠->就绪。
3   R   3074    schedule                进程3运行(进程3由于长时间睡觉,其优先级变高,进入就绪队列后立即得到调度执行) --- */
3   W   3074    sys_pause       //进程3主动睡觉,让出CPU
0   R   3074    schedule        //因为无事可做,进程0再次执行
4   J   3099    wake_up         //用户接着输入命令,唤醒shell(shell等待了0.53s)
4   R   3100    schedule        //shell执行
4   W   3100    interruptible_sleep_on  //shell等待用户输入
0   R   3100    schedule        //因为无事可做,进程0再次执行
4   J   3416    wake_up         //用户接着输入命令,唤醒shell(shell等待了3.16s)
4   R   3417    schedule        //shell执行
4   W   3417    interruptible_sleep_on  //shell等待用户输入
0   R   3417    schedule        //因为无事可做,进程0再次执行
4   J   3457    wake_up         //用户接着输入命令,唤醒shell(shell等待了0.4s)
4   R   3457    schedule        //shell执行
4   W   3457    interruptible_sleep_on  //shell等待用户输入
0   R   3457    schedule        //因为无事可做,进程0再次执行
4   J   3623    wake_up         //用户接着输入命令,唤醒shell(shell等待了1.66s)
4   R   3623    schedule        //shell执行
4   W   3623    interruptible_sleep_on  //shell等待用户输入
0   R   3623    schedule        //因为无事可做,进程0再次执行
4   J   3687    wake_up         //用户接着输入命令,唤醒shell(shell等待了0.64s)
4   R   3687    schedule        //shell执行
4   W   3687    interruptible_sleep_on  //shell等待用户输入
0   R   3687    schedule
    
    
    ....
    
    
0   R   7494    schedule                //因为无事可做,进程0再次运行
4   J   7560    wake_up                 //用户继续输入命令,唤醒shell(shell等待了0.66s)
4   R   7560    schedule                //shell执行
4   W   7560    interruptible_sleep_on  //shell等待用户输入
0   R   7561    schedule                //因为无事可做,进程0再次执行
4   J   7617    wake_up                 //用户继续输入命令,唤醒shell(shell等待了0.56s)
4   R   7617    schedule                //shell执行
4   W   7617    interruptible_sleep_on  //shell等待用户输入
0   R   7617    schedule                //因为无事可做,进程0再次执行
4   J   8090    wake_up                 //用户继续输入命令,唤醒shell(shell等待了3.73s),该命令输入完毕(用户输入该命令总共经历了67.39s,一分钟的时间,用户输入不可能这么长!那么这个命令是什么?全部是由用户输入?)
4   R   8090    schedule                //shell执行该命令
8   N   8092    copy_process            /* --- shell新建进程8(估计是gcc编译process.c生成可执行文件process的命令)。
8   J   8093    copy_process                    进程8新建后进入就绪队列 --- */
4   W   8093    sys_waitpid             //shell开始等待进程8退出
8   R   8093    schedule                //进程8开始运行
9   N   8098    copy_process            /* --- 进程8新建进程9(估计是gcc用于编译process.c的预处理子进程?)。
9   J   8098    copy_process                    进程9新建后进入就绪队列 --- */
8   W   8099    sys_waitpid             //进程8开始等待进程9退出
9   R   8099    schedule                //进程9开始运行
9   E   8159    do_exit                 //进程9退出(运行了0.6s)
8   J   8159    schedule                /* --- 进程8此前在等待进程9退出,被阻塞。进程9退出后,重新进入就绪队列。
8   R   8159    schedule                        进程8运行 --- */
10  N   8160    copy_process            /* --- 进程8新建进程10(估计是gcc用于编译process.c的编译子进程,生成汇编代码?)。
10  J   8161    copy_process                    进程10新建后进入就绪队列 --- */
8   W   8161    sys_waitpid             //进程8开始等待进程10退出
10  R   8161    schedule                //进程10开始运行
10  E   8295    do_exit                 //进程10退出(运行了1.34s)
8   J   8295    schedule                /* --- 进程8此前在等待进程10退出,被阻塞。进程10退出后,重新进入就绪队列。
8   R   8295    schedule                        进程8继续运行 --- */
11  N   8295    copy_process            /* --- 进程8新建进程11(估计是gcc用于编译process.c的汇编子进程,生成二进制目标文件?)。
11  J   8296    copy_process                    进程11新建后进入就绪队列 --- */
8   W   8296    sys_waitpid             //进程8开始等待进程11退出
11  R   8296    schedule                //进程11开始运行
11  E   8341    do_exit                 //进程11退出(运行了0.45s)
8   J   8341    schedule                /* --- 进程8此前在等待进程11退出,被阻塞。进程11退出后,重新进入就绪队列。
8   R   8341    schedule                        进程8继续运行 --- */
12  N   8343    copy_process            /* --- 进程8新建进程12(估计是gcc用于编译process.c的链接子进程,生成可执行文件process?)。
12  J   8344    copy_process                    进程12新建后进入就绪队列 --- */
8   W   8344    sys_waitpid             //进程8开始等待进程12退出
12  R   8344    schedule                //进程12开始运行
12  E   8425    do_exit                 //进程12退出(运行了0.81s)
8   J   8425    schedule                /* --- 进程8此前在等待进程12退出,被阻塞。进程12退出后,重新进入就绪队列。
8   R   8425    schedule                        进程8继续运行 --- */
8   E   8427    do_exit                 //进程8退出(gcc编译process.c生成process可执行文件经历了3.34s)
4   J   8427    schedule                /* --- shell此前在等待进程8退出,被阻塞。进程8退出后,重新进入就绪队列
4   R   8427    schedule                        shell运行 --- */
13  N   8427    copy_process            /* --- shell新建进程13(不知道是做什么的进程)。
13  J   8428    copy_process                    进程13新建后进入就绪队列 --- */
4   W   8428    sleep_on                //进程4进入不可中断睡眠状态
13  R   8428    schedule                //进程13开始运行
4   J   8430    wake_up                 //进程13唤醒睡觉的进程4,进程4进入就绪队列
13  E   8431    do_exit                 //进程13很快退出(运行了0.03s)
4   R   8431    schedule                //shell运行
4   W   8432    interruptible_sleep_on  //shell等待用户输入命令
0   R   8432    schedule                //因为无事可做,进程0再次执行
4   J   9737    wake_up                 //用户开始输入命令,唤醒了shell(从等待用户输入命令到用户开始输入命令,总共经历了13s!为什么会这么长时间?)
4   R   9737    schedule                //shell运行
4   W   9737    interruptible_sleep_on  //shell等待用户输入命令
0   R   9737    schedule                //因为无事可做,进程0再次执行
4   J   9790    wake_up                 //用户接着输入命令,唤醒了shell(shell等待了0.53s)
4   R   9790    schedule                //shell运行
4   W   9790    interruptible_sleep_on  //shell等待用户输入命令
    
    ......
0   R   11136   schedule                //因为无事可做,进程0再次执行
4   J   11195   wake_up                 //用户接着输入命令,唤醒了shell(shell等待了0.59s)
4   R   11195   schedule                //shell运行
4   W   11195   interruptible_sleep_on  //shell等待用户输入命令
0   R   11196   schedule                //因为无事可做,进程0再次执行
4   J   12048   wake_up                 //用户接着输入命令,唤醒了shell(shell等待了8.52s),该命令输入完毕(用户输入该命令总共经历了231s!输入不可能需要这么长时间!说明计时单位并不是10ms???)
4   R   12048   schedule                //shell执行该命令(即我们自己编写的样本程序process可执行文件)
14  N   12049   copy_process            /* --- shell新建进程14(即process.c的main()函数)。
14  J   12049   copy_process                    进程14新建后进入就绪队列 --- */
4   W   12049   sys_waitpid             //shell开始等待进程14(process.c的main()函数)退出
14  R   12050   schedule                //进程14(process.c的main()函数)开始运行
15  N   12051   copy_process            /* --- main()函数新建进程15(N1 node)。
15  J   12051   copy_process                    进程15(N1 node)新建后进入就绪队列 --- */
16  N   12052   copy_process            /* --- main()函数新建进程16(N2 node)。
16  J   12052   copy_process                    进程16(N2 node)新建后进入就绪队列 --- */
14  W   12053   sys_waitpid             //进程14(现在为process.c的main()函数中的Root)开始等待进程15(N1 node)和进程16(N2 node)退出
16  R   12053   schedule                //进程16(N2 node)开始运行
17  N   12053   copy_process            /* --- 进程16新建进程17(N5 node)。
17  J   12053   copy_process                    进程17(N5 node)新建后进入就绪队列 --- */
16  W   12054   sys_pause               //进程16(N2 node)主动睡觉
17  R   12054   schedule                //进程17(N5 node)开始运行
17  J   12069   schedule                /* --- 进程17(n5 node)时间片(15个滴答)耗完,从运行->就绪(运行了0.15s),让出CPU。
15  R   12069   schedule                        进程15(N1 node)开始运行 --- */
18  N   12069   copy_process            /* --- 进程15(N1 node)新建进程18(N3 node)。
18  J   12069   copy_process                    进程18(N3 node)新建后进入就绪队列 --- */
19  N   12070   copy_process            /* --- 进程15(N1 node)新建进程19(N4 node)。
19  J   12070   copy_process                    进程19(N4 node)新建后进入就绪队列 --- */
15  J   12084   schedule                /* --- 进程15(N1 node)时间片耗完,从运行->就绪(运行了0.15s),让出CPU。
19  R   12084   schedule                        进程19(N4 node)开始运行 --- */
19  J   12099   schedule                /* --- 进程19(N4 node)时间片耗完,从运行->就绪(运行了0.15s),让出CPU。
18  R   12099   schedule                        进程18(N3 node)开始运行 --- */
18  J   12114   schedule                /* --- 进程18(N3 node)时间片耗完,从运行->就绪(运行了0.15s),让出CPU。
19  R   12114   schedule                        进程19(N4 node)运行 --- */
19  J   12129   schedule                /* --- 进程19(N4 node)时间片耗完,从运行->就绪(运行了0.15s),让出CPU。 
18  R   12129   schedule                        进程18(N3 node)开始运行 --- */ 
18  J   12144   schedule                /* --- 进程18(N3 node)时间片耗完,从运行->就绪(运行了0.15s),让出CPU。 
17  R   12144   schedule                        进程17(N5 node)运行 --- */ 
16  J   12159   schedule                /* --- 进程16(N2 node)被唤醒。
17  J   12159   schedule                        进程17(N5 node)时间片耗完,从运行->就绪(运行了0.15s),让出CPU。
16  R   12159   schedule                        进程16(N2 node)运行 --- */

猜你喜欢

转载自blog.csdn.net/qq_38307618/article/details/124492119