Linux 内核--任务0的调度切换

本文分析基于Linux 0.11内核,转载请标明出处

http://blog.csdn.net/yming0221/archive/2011/06/09/6533865.aspx。

 

 

main.c中在move_to_user_mode()之后,切换到用户模式下运行,task0然后执行fork()创建进程task1来执行init()函数。init()函数如下:

[cpp] view plain copy print ?
  1. void init (void)  
  2. {  
  3.   int pid, i;  
  4.   // 读取硬盘参数包括分区表信息并建立虚拟盘和安装根文件系统设备。   
  5.   // 该函数是在25 行上的宏定义的,对应函数是sys_setup(),在kernel/blk_drv/hd.c,71 行。   
  6.     setup ((void *) &drive_info);  
  7.     (void) open ("/dev/tty0", O_RDWR, 0);   // 用读写访问方式打开设备“/dev/tty0”,   
  8.   // 这里对应终端控制台。   
  9.   // 返回的句柄号0 -- stdin 标准输入设备。   
  10.     (void) dup (0);     // 复制句柄,产生句柄1 号 -- stdout 标准输出设备。   
  11.     (void) dup (0);     // 复制句柄,产生句柄2 号 -- stderr 标准出错输出设备。   
  12.     printf ("%d buffers = %d bytes buffer space/n/r", NR_BUFFERS, NR_BUFFERS * BLOCK_SIZE); // 打印缓冲区块数和总字节数,每块1024 字节。   
  13.     printf ("Free mem: %d bytes/n/r", memory_end - main_memory_start);  //空闲内存字节数。   
  14.   // 下面fork()用于创建一个子进程(子任务)。对于被创建的子进程,fork()将返回0 值,   
  15.   // 对于原(父进程)将返回子进程的进程号。所以180-184 句是子进程执行的内容。该子进程   
  16.   // 关闭了句柄0(stdin),以只读方式打开/etc/rc 文件,并执行/bin/sh 程序,所带参数和   
  17.   // 环境变量分别由argv_rc 和envp_rc 数组给出。参见后面的描述。   
  18.   if (!(pid = fork ()))  
  19.     {  
  20.       close (0);  
  21.       if (open ("/etc/rc", O_RDONLY, 0))  
  22.     _exit (1);      // 如果打开文件失败,则退出(/lib/_exit.c,10)。   
  23.       execve ("/bin/sh", argv_rc, envp_rc); // 装入/bin/sh 程序并执行。   
  24.       _exit (2);        // 若execve()执行失败则退出(出错码2,“文件或目录不存在”)。   
  25.     }  
  26.   // 下面是父进程执行的语句。wait()是等待子进程停止或终止,其返回值应是子进程的进程号(pid)。   
  27.   // 这三句的作用是父进程等待子进程的结束。&i 是存放返回状态信息的位置。如果wait()返回值不   
  28.   // 等于子进程号,则继续等待。   
  29.   if (pid > 0)  
  30.     while (pid != wait (&i))  
  31.       /* nothing */ ;  
  32.   // 如果执行到这里,说明刚创建的子进程的执行已停止或终止了。下面循环中首先再创建一个子进程,   
  33.   // 如果出错,则显示“初始化程序创建子进程失败”的信息并继续执行。对于所创建的子进程关闭所有   
  34.   // 以前还遗留的句柄(stdin, stdout, stderr),新创建一个会话并设置进程组号,然后重新打开   
  35.   // /dev/tty0 作为stdin,并复制成stdout 和stderr。再次执行系统解释程序/bin/sh。但这次执行所   
  36.   // 选用的参数和环境数组另选了一套(见上面165-167 行)。然后父进程再次运行wait()等待。如果   
  37.   // 子进程又停止了执行,则在标准输出上显示出错信息“子进程pid 停止了运行,返回码是i”,然后   
  38.   // 继续重试下去…,形成“大”死循环。   
  39.   while (1)  
  40.     {  
  41.       if ((pid = fork ()) < 0)  
  42.     {  
  43.       printf ("Fork failed in init/r/n");  
  44.       continue;  
  45.     }  
  46.       if (!pid)  
  47.     {  
  48.       close (0);  
  49.       close (1);  
  50.       close (2);  
  51.       setsid ();  
  52.       (void) open ("/dev/tty0", O_RDWR, 0);  
  53.       (void) dup (0);  
  54.       (void) dup (0);  
  55.       _exit (execve ("/bin/sh", argv, envp));  
  56.     }  
  57.       while (1)  
  58.     if (pid == wait (&i))  
  59.       break;  
  60.       printf ("/n/rchild %d died with code %04x/n/r", pid, i);  
  61.       sync ();  
  62.     }  
  63.   _exit (0);            /* NOTE! _exit, not exit() */  
  64. }  
 

init进程通过fork()产生子进程,产生的子进程开始读取硬盘参数包括分区表信息并建立虚拟盘和安装根文件系统设备,打开设备,并运行sh程序

,当该进程异常结束,就会循环重试上述过程。然而进程0这时会运行到代码

for(;;) pause()

处,pause()函数是系统调用,它也被声明为内联函数,通过int 0x80调用系统调用sys_pause()。

[cpp] view plain copy print ?
  1. int  
  2. sys_pause (void)  
  3. {  
  4.   current->state = TASK_INTERRUPTIBLE;  
  5.   schedule ();  
  6.   return 0;  
  7. }  
 

该系统调用所做的任务就是将当前任务的运行状态改为可中断运行状态,然后执行调度函数schedule()。

[cpp] view plain copy print ?
  1. void  
  2. schedule (void)  
  3. {  
  4.   int i, next, c;  
  5.   struct task_struct **p;   // 任务结构指针的指针。   
  6.   /* check alarm, wake up any interruptible tasks that have got a signal */  
  7.   /* 检测alarm(进程的报警定时值),唤醒任何已得到信号的可中断任务 */  
  8.   // 从任务数组中最后一个任务开始检测alarm。   
  9.   for (p = &LAST_TASK; p > &FIRST_TASK; --p)  
  10.     if (*p)  
  11.       {  
  12.     // 如果任务的alarm 时间已经过期(alarm<jiffies),则在信号位图中置SIGALRM 信号,然后清alarm。   
  13.     // jiffies 是系统从开机开始算起的滴答数(10ms/滴答)。定义在sched.h 第139 行。   
  14.     if ((*p)->alarm && (*p)->alarm < jiffies)  
  15.       {  
  16.         (*p)->signal |= (1 << (SIGALRM - 1));  
  17.         (*p)->alarm = 0;  
  18.       }  
  19.     // 如果信号位图中除被阻塞的信号外还有其它信号,并且任务处于可中断状态,则置任务为就绪状态。   
  20.     // 其中'~(_BLOCKABLE & (*p)->blocked)'用于忽略被阻塞的信号,但SIGKILL 和SIGSTOP 不能被阻塞。   
  21.     if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&  
  22.         (*p)->state == TASK_INTERRUPTIBLE)  
  23.       (*p)->state = TASK_RUNNING;    //置为就绪(可执行)状态。   
  24.       }  
  25.   /* this is the scheduler proper: */  
  26.   /* 这里是调度程序的主要部分 */  
  27.   while (1)  
  28.     {  
  29.       c = -1;  
  30.       next = 0;  
  31.       i = NR_TASKS;  
  32.       p = &task[NR_TASKS];  
  33.       // 这段代码也是从任务数组的最后一个任务开始循环处理,并跳过不含任务的数组槽。比较每个就绪   
  34.       // 状态任务的counter(任务运行时间的递减滴答计数)值,哪一个值大,运行时间还不长,next 就   
  35.       // 指向哪个的任务号。   
  36.       while (--i)  
  37.     {  
  38.       if (!*--p)  
  39.         continue;  
  40.       if ((*p)->state == TASK_RUNNING && (*p)->counter > c)  
  41.         c = (*p)->counter, next = i;  
  42.     }  
  43.       // 如果比较得出有counter 值大于0 的结果,则退出124 行开始的循环,执行任务切换(141 行)。   
  44.       if (c)  
  45.     break;  
  46.       // 否则就根据每个任务的优先权值,更新每一个任务的counter 值,然后回到125 行重新比较。   
  47.       // counter 值的计算方式为counter = counter /2 + priority。[右边counter=0??]   
  48.       for (p = &LAST_TASK; p > &FIRST_TASK; --p)  
  49.     if (*p)  
  50.       (*p)->counter = ((*p)->counter >> 1) + (*p)->priority;  
  51.     }  
  52.   switch_to (next);     // 切换到任务号为next 的任务,并运行之。   
  53. }  
 

该段代码有很多精巧之处,如果让我自己来实现同样的功能,代码可能会很繁琐。

精巧之处:

1、i起初赋值64,循环的时候先进行变量的--,然后再判断,这样正好执行63次,task0没有必要循环判断。

2、c起初赋值为-1,next赋值0,这样可以解决当循环63次后,task[1]--task[63]没有相应的任务在执行,这时,执行下一句break后,跳

出死循环,执行swtich_to(next)语句,正好切换到task[0],task0这时继续循环执行pause()语句,调用调度函数。所以说task0是个闲置的

任务,只有task数组没有其他任务的时候才执行task0,让task0继续执行调度函数。重新调度进程运行。

你可能感兴趣的:(Linux 内核--任务0的调度切换)