初始化程序
Posted Jiamings
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初始化程序相关的知识,希望对你有一定的参考价值。
赵炯;《Linux 内核完全注释 0.11 修正版 V3.0》
系统在执行完 boot/ 目录中的 head.s 程序后,就会将执行权交给 main.c —— 包括了内核初始化的所有工作。
main.c 程序
- 根据 setup.s 中的系统参数设置系统的根文件设备号以及一些内存全局变量;
- 硬件初始化工作:陷阱门、块设备、字符设备和tty、人工设置第一个任务;
- 开启中断;
- 切换到任务0 —— cpu 从 0 特权级切换到了第 3 特权级,main.c 的主程序就在任务 0 中,第一次调用 fork(),创建init()进程;
void main(void) /* This really IS void, no error here. */
/* The startup routine assumes (well, ...) this */
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10);
memory_end &= 0xfffff000;
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024)
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024;
main_memory_start = buffer_memory_end;
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
mem_init(main_memory_start,memory_end);
trap_init();
blk_dev_init();
chr_dev_init();
tty_init();
time_init();
sched_init();
buffer_init(buffer_memory_end);
hd_init();
floppy_init();
sti();
move_to_user_mode();
if (!fork()) /* we count on this going ok */
init();
/*
* NOTE!! For any other task pause() would mean we have to get a
* signal to awaken, but task0 is the sole exception (see schedule())
* as task 0 gets activated at every idle moment (when no other tasks
* can run). For task0 pause() just means we go check if some other
* task can run, and if not we return here.
*/
for(;;) pause();
main.c 把自己手工移动到进程0 中运行,并使用fork()首次创建出进程1,并在其中调用init() 函数,在该函数中程序将继续进行应用环境初始化并执行shell 登录程序,而原进程0 则会在系统空闲时被调度执行,进程0 也被称为idle进程,此时进程0 仅执行pause 系统调用,并又会调用调度函数。
进程1中init() 函数功能:
- 安装根文件系统;
- 显示系统信息;
- 运行系统初始资源配置文件rc 中的命令;
- 执行用户登录shell 程序;
- 调用 setup() 系统调用,判断是否需要建立虚拟盘(编译时决定,如果存在,内核首先尝试把根文件系统加载到内存的虚拟盘区),收集硬盘设备分区表并安装根文件系统;
- init() 打开一个终端设备tty0,并复制其文件描述符以产生标准输入 stdin、标准输出stdout、错误输出stderr 设备。内核随后利用这些描述符在终端上显示一些系统信息。
- init() 建立进程2,为建立用户交互使用环境而执行一些初始化配置操作,内核调用 /bin/sh 程序运行了配置文件 etc/rc 中设置的命令,内核以非交互形式执行/bin/sh,从而实现执行/etc/rc 文件中的命令,当该文件中的内容执行完毕后,进程2 结束;
- init() 建立新进程,在该进程中创建新的会话,并以登录shell 方式再次执行程序/bin/sh,以创建用户交互shell 环境,然后init进程等待该进程。登录shell 与之前非交互式shell 有所不同,登录shell 的第0 个命令行参数的第一个字符一定是减号,标志是作为登录shell 运行/bin/sh 的,若用户在命令行界面执行了 exit 或者 logout 命令,那么在显示一条当前登录shell 退出的信息后,系统再创建登录 shell。
由于创建新进程的过程是通过完全复制父进程代码段和数据段的方式实现的,因此在首次使用 fork() 创建新进程 init 时,为了确保新进程用户态中没有进程0 的多余信息,要求进程0 在创建首个新进程1 之前不要使用其用户态栈,即要求任务0 不要调用函数。实现办法是采用内联(inline)形式。
使用系统调用完成(使用任务的内核态栈而不是用户栈),从任务 1 执行过出/入栈操作后,任务0和任务1的用户栈才变成相互独立的栈。由于内核调度进程运行是随机的,有可能在任务0 创建了任务 1后,仍然先允许任务0,因此任务0执行fork()操作后,随后的 pause() 函数也必须采用内嵌形式实现,避免任务 0 在任务 1 之前使用用户栈。
当系统中一个进程执行过 execve() 调用后,进程的代码和数据区会位于系统的主内存区中,因此系统可以利用 COW 技术来处理其它新进程的创建和执行。
对于 Linux 来说,所有任务都是在用户模式下运行的,包括很多系统应用程序,如shell程序、网络子系统程序等。内核源代码下的 lib/ 目录下的库文件就是专门为这里新创建的进程提供函数支持,内核代码本身不使用这些库函数。
CMOS 信息
CMOS 内存是由电池供电的 64/ 128 字节的内存块,通常是系统实时钟芯片RTC的一部分,用来存放时钟和日期信息、系统配置数据。
CMOS 的地址空间在基本地址空间之外,因此其中不包括可执行代码。要访问它需要通过端口 0x70(地址端口)、0x71(数据端口)。为了读取指定偏移位置的字节,必须首先使用 OUT
指令向地址端口0x70发送指定字节的偏移位置值,然后使用 IN
指令从 0x71 读取指定的字节信息。对于写操作也需要首先向地址端口 0x70 发送指定字节的偏移值,然后把数据写到数据端口0x71中去。
调用fork()创建新进程
fork() 是一个系统调用函数,该系统调用复制当前进程,并在进程表中创建一个与原进程几乎完全一样的新表项,并执行同样的的代码,但该新进程拥有自己的数据空间和环境参数,创建新进程的主要用途在于新进程中使用excec()函数去执行其他不同的程序。
在 fork() 调用返回位置处,父进程将恢复执行,而子进程则开始执行。在父进程中,调用fork()返回的是子进程的进程标识号PID,而在子进程中fork()返回的是 0 值,虽然此时还是在同样一程序中执行,但是已经开始岔开,各自执行自己的代码。
虽然子进程继承了父进程的全部资源,但是只要两个进程创建的程序和数据没有冲突,则它们可以并发运行。
会话期 session
程序是一个可执行的文件,而进程是一个执行中的程序实例。 在内核中,每个进程都是用一个不同的大于零的正整数来标识,称为进程标识号 pid,而一个进程可以通过 fork() 创建一个或多个子进程,这些进程就可以构成一个进程组。
cat main.c | grep for | more
其中,所有命令都属于一个进程组。每个进程组都有一个唯一的进程组标识号 gid,每一个进程组有一个称为组长的进程,组长进程就是其进程号 pid 等于进程组号 gid 的进程。
一个进程可以通过调用 setpid() 来参加一个现有的进程组或者创建一个新的进程组。
进程组最常见的应用是使用 Ctrl+C 来同时终止进程组中的所有进程。
会话期是一个或者多个进程组的集合,通常,用户登录后所执行的所有程序都属于一个会话期,而其登录的 shell 则是会话期的首进程(session leader),并且它所使用的终端就是会话期的控制终端,因此会话期首进程通常也被称为控制进程。当我们退出登录时,所有属于我们这个会话期的进程都将终止。setsid() 函数用来建立一个新的会话期。一个终端只能作为一个会话期的控制终端。控制终端对应于/dev/tty设备文件,因此若一个进程需要访问控制终端,可以直接对/dev/tty文件进行读写操作。
对于 0.11 版本内核,只要根文件系统是一个 MINIX 文件系统,并且其中只要包含 /etc/rc、/bin/sh、/dev/*、/etc、/dev、/bin、/home、/home/root就可以构成一个最简单的根文件系统,让 Linux 运行起来。
内核通过执行 sched.c 程序中的调度函数 schedule()、system_call.s 中的定时时钟中断过程 _timer_interrupt 来进行进程调度运行的,内核设定每 10ms 发出一次时钟中断,通过调用 do_timer() 函数检查所有进程的当前执行情况来确定进程的下一步状态,进程在执行过程中也可以调用 sleep_on() 间接调用 schedule() 函数,交出 CPU 使用权。
以上是关于初始化程序的主要内容,如果未能解决你的问题,请参考以下文章