实验4 进程运行轨迹跟踪与统计
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;elsereturn -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)运行 --- */