Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1087012
  • 博文数量: 321
  • 博客积分: 7872
  • 博客等级: 少将
  • 技术积分: 2120
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-16 09:06
文章分类

全部博文(321)

文章存档

2017年(1)

2016年(1)

2015年(12)

2014年(17)

2013年(78)

2012年(15)

2011年(17)

2010年(67)

2009年(102)

2008年(11)

分类: LINUX

2014-05-22 15:03:13

一.说明

本文以linux-2.4.10 为例主要分析Linux启动过程,用户登录过程,及内核进程调度schedule简单介绍。默认系统平台是自己的i386 架构的pc。


二.Linux启动过程,用户登录过程

在进行schedule 分析之前有必要简单说明一下系统启动过程,内存分配使用等。这样才能自然过渡到schedule 模块。

首先是Linux各个功能模块之间的依赖关系:

Linux内核进程调度schedule介绍 - coolwindy - miss fall
 


可见进程调度是整个内核的核心。但这部分,我想说明的是,我的pc是怎样把操作系统从硬盘装载到内存中,并启动进程调度模块的。然后才是后面对schedule的具体分析。

首先,启动操作系统部分,涉及到到三个文件:/arch/i386/boot/bootsect.s、/arch/i386/boot/setup.s、/arch/i386/boot/compressed/head.s。编译安装好一个Linux系统后,bootsect.s模块被放置在可启动设备的第一个扇区(磁盘引导扇区,512字节)。那么下面开始启动过程,三个文件在内存中的分布与位置的移动如下图。

Linux内核进程调度schedule介绍 - coolwindy - miss fall
 


在经过上图这一系列过程后,程序跳转到system模块中的初始化程序init中执行,即/init/main.c文件。该程序执行一系列的初始化工作,如寄存器初始化、内存初始化、中断设置等。之后内存的分配如下图:

Linux内核进程调度schedule介绍 - coolwindy - miss fall
 


此后,CPU有序地从内存中读取程序并执行。前面的main从内核态移动到用户态后,操作系统即建立了任务0,即进程调度程序。之后再由schedule模块进行整个Linux操作系统中进程的创建(fork),调度(schedule),销毁(exit)及各种资源的分配与管理等操作了。值得一说的是schedule将创建的第一个进程是init(pid=1),请注意它不是前面的/init/main.c程序段。如果是在GNU/Debian系统下,init 进程将依次读取rcS.d,rcN.d(rc0.d~rc6.d其中一个文件夹中的所有脚本),rc.local三个run command脚本等,之后系统的初始化就完成了,一系列系统服务被启动了,系统进入单用户或者多用户状态。然后init 读取/etc/inittab,启动终端设备((exec)getty)供用户登陆,如debian中会启动6个tty,你可以用组合键ctrl+alt+Fn(F1~F6)来切换。


(下面是第二部分,用户登录过程)

如果是终端登录的话,当用户键入用户名后,getty的工作就完成了。然后它会调用login程序,并将用户名传给login。login能执行多项工作。因为它得到了用户名,于是可以调用getpwnam取得命令文件登录项,然后调用getpass(3)以显示提示“Password:”,接着用户键入口令,它再调用crypt(3)将口令加密,并与该用户在阴影口令文件中pw_passwd字段比较。

如果是网络登录的话,作为系统调用的一部分,init调用一个shell,执行rc脚本,启动守护进程inetd。脚本结束后,inetd的父进程变为init。接着inetd等待TCP/IP连接,当一个连接请求到达时,它执行一次fork,生成子进程执行适当的程序。比如,一个telnet请求到达主机,那么就会fork并exec一个telnetd服务进程。然后,telnetd打开一个伪终端设备,并用fork分成两个进程。父进程处理网络连接的通信,子进程则执行login程序,处理用户的登录与身份验证,过程和上一段的“终端登录”基本一样。


到这里就知道了Linux怎样启动进程调度模块了,也知道了进程调度模块启动的第一个进程init及之后的系统初始化和登陆流程。下面就回过头来分析schedule代码及其相关函数调用。


三.进程调度涉及的数据结构

文件:/linux/include/linux/sched.h

下面只简单介绍数据结构task_struct中的两个字段。

在Linux中,进程(Linux中用轻量级的进程来模拟线程)使用的核心数据结构。一个进程在核心中使用一个task_struct结构来表示,包含了大量描述该进程的信息,其中与调度器相关的信息主要包括以下几个:

1. state

volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */

Linux的进程状态主要分为三类:可运行的(TASK_RUNNING,相当于运行态和就绪态);被挂起的(TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE和TASK_STOPPED);不可运行的(TASK_ZOMBIE),调度器主要处理的是可运行和被挂起两种状态下的进程,其中TASK_STOPPED又专门用于SIGSTP等IPC信号的响应,而TASK_ZOMBIE指的是已退出而暂时没有被父进程收回资源的"僵死"进程。

2. counter

long counter;

该属性记录的是当前时间片内该进程还允许运行的时间。

(即进程调度算法)

文件:/kernel/sched.c

1.上下文切换

从一个进程的上下文切换到另一个进程的上下文,因为其发生频率很高,所以通常都是调度器效率高低的关键。由switch_to()实现,而它的代码段在schedule()过程中调用,以一个宏实现。

switch_to()函数正常返回,栈上的返回地址是新进程的task_struct::thread::eip,即新进程上一次被挂起时设置的继续运行的位置(上一次执行switch_to()时的标号"1:"位置)。至此转入新进程的上下文中运行。

这其中涉及到wakeup,sleepon等函数来对进程进行睡眠与唤醒操作。

2.选择算法

Linux schedule()函数将遍历就绪队列中的所有进程,调用goodness()函数计算每一个进程的权值weight,从中选择权值最大的进程投入运行。

Linux的调度器主要实现在schedule()函数中。

转自:http://coolwinding.blog.163.com/blog/static/11224093920108243335514/

阅读(1046) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~