分类: LINUX
2008-04-15 10:58:17
今天用了一个小时的时间把0.11核的初始化程序main.c浏览完了,对Linux的初始化过程有了一个整体上的了解,当然main里面调用了多个初始化子程序,我都暂且没深入进去看,我打算以后看到某个专题的时候再看吧。和以前一样,老规矩,先说一下初始化程序的大概过程,在深入文件具体说一下。
一、概述
main.c主程序工作在任务0中, main先物理内存进行划分和分配,然后对各个设备进行初始化,最后创建进程1(init进程),并从进程1的init()运行,进程1的init()会继续进行系统的初始化并再创建一个非交互式shell进程:进程2,在进程2主要是执行rc配置文件,进程2结束后会返回到进程1中,进程1再创建一个shell登陆进程,在这里用户就可以使用Linux的shell命令环境了。
二、main.c结构
1)保存根设备号和划分内存:设置高速缓存末地址、物理内存大小、内存主区 开始地址。
2)硬件初始化:陷阱门、块设备、字符设备、tty、开机时间、调度程序、硬盘等
3)切换到用户模式,创建进程1,在进程1中从init()开始执行,而进程0处在空闲状态。
进程1的init()中:
4)系统调用setup:安装根文件系统设备
5)打开设备tty0:它对应终端控制台,以读写方式打开作为输入,并再复制两个句柄作为普通输出和错误输出。
6)创建子shell进程(进程2):创建进程2,在进程2中,关闭输入设备句柄(0),并重新将输入句柄定向到rc,使系统能加载rc文件,最后加载rc文件执行,完成配置工作。而进程1则等待进程2的结束。
7)建立shell登陆进程:创建新进程,在新进程中,首先关闭三个句柄(0,1,2);然后创建一个会话期,接着重新打开tty0作为输入设备,复制两个句柄作为stdout和stderr;最后加载shell登陆程序。而在进程1中,等待创建进程的结束并开始新一轮的创建新进程的循环。
以上就是Linux系统初始化的大体过程,在这里需要说明几个问题:
1)系统调用改作内联函数
程序中的三个系统调用fork()、pause()和setup()是采用的内联函数的形式而不是进行系统调用。这样做的原因是这样的,因为在创建进程1之前任务0就已经切换到用户模式下了,这个使用的栈是用户栈。进程1创建后,它用的代码和数据是核进程0共享的,也就是说进程1用的栈和进程0用的栈是一个栈,如果在进程1中发生入栈操作,根据内存管理提供的写时复制规则就会引起页面写保护异常,系统就会在物理内存中申请新的页面并将进程0栈中相应的页面复制到进程1新分配的物理页中,这样问题就来了,如果在复制之前进程0用了用户栈,那么复制时,也会把进程0栈的内容复制到进程1的栈中,这样进程1中就会有进程0的多余信息。这样不好。而内联函数就可以解决这个问题,内联函数的调用不会引起用户栈进出栈操作,即使里面的中断指令int会使用栈,但它操作的是核心态栈,核心态栈是独立的,存在于task_struct所在页面,这样就会保证在进程1执行前进程0的用户栈是空的了。
2)会话期
上面提到了创建会话期。下面说一下会话期。一个进程调用多个fork()创建多个子进程,这些进程会构成一个进程组,每个组内的进程除了用自己的进程号之外还有它所在组的进程组号(也就是组长的进程号),而会话期则是一个或者多个进程组的集合。用户登陆后所执行的所有程序都属于一个会话期,而登陆shell就是会话期的首进程,也就是控制终端,当他=用户退出登录的时候,系统会结束这会话期的所用进程,这也是会话期的主要用途之一。
3)用户登录。
上面的main.c中的init()函数中,直接创建进程运行shell登陆程序,而在实际应用中不是这样的。在实际应用中,在这个地方会执行系统初始化程序init.c,在这个程序根据rc配置信息,为每个允许登陆的终端设备创建一个子进程,并在子进程中运行getty程序,这个程序会在终端上显示登陆信息(用户名密码),用户输入用户信息后,getty被替换成login程序,login程序在验证了用户信息的正确性后,调用shell程序,进入交互式界面。
4)CMOS信息
内核需要直接从CMOS中读取时间、内存等信息,读取原理是这样的:系统的信息储存在CMOS的不同偏移处,系统先向CMOS的70地址端口写入偏移地址,在从71数据端口利用int读出数据。