Linux 0.11内核源码
<1> 代码路径:init/main.c
static inline _syscall0(int,fork) void main(void) { if (!fork()) { /* we count on this going ok */ init(); } }
由代码中对fork()的声明,可知调用fork函数,实际上是执行到include/unistd.h中的宏函数syscall0中去。
测试举例:如下代码中,函数myfork()的调用,根据static inline声明,将跳转到函数mysyscall()中……
1 #include <stdio.h> 2 3 #define NR_myfork 2 4 5 #define mysyscall(type, name) \ 6 type name(void) \ 7 { \ 8 printf("%d\n", NR_##name); \ 9 } 10 11 static inline mysyscall(int, myfork) 12 int main(void) 13 { 14 myfork(); 16 }
<2> 代码路径:include/unistd.h
#define __NR_fork 2 #define _syscall0(type,name) \ type name(void) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name)); \ if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ }init/main.c中static inline _syscall0(int, fork),syscall0展开后,如下:
int fork(void) { long __res; __asm__ volatile ("int $0x80" // int 0x80软中断是所有系统调用函数的总入口 : "=a" (__res) // 第1个冒号之后是输出部分,将_res赋给eax : "0" (__NR_fork)); // 第2个冒号之后是输入部分,“0”为eax寄存器,文件kernel/system_call.s; __NR_fork在宏定义中为2 if (__res >= 0) // int 0x80中断返回后,将执行这一句 return (int) __res; errno = -__res; return -1; }
详细的执行步骤:
step 1: 先执行“0” (__NR_fork)。将__NR_fork(即2)赋值给eax寄存器。编号2为sys_fork()函数在sys_call_table[]中的偏移值。(文件 include/linux/sys.h)
step 2: 紧接着执行"int $0x80"。产生一个软中断,CPU从3特权级的进程1代码跳到0特权级内核代码中执行。中断使CPU硬件自动将SS、ESP、EFLAGS、CS、EIP这5个寄存器的数值按照这个顺序压入init_task中的进程0内核栈。(这些压栈的数据将在后续的copy_process()函数中用来初始化进程1的TSS。其中,压栈的EIP指向当前指令"int $0x80"的下一行,即"if (__res >= 0)"这一行。这一行就是进程0从fork函数系统调用中断返回后第一条指令的位置,也是进程1开始执行的第一条指令位置。)
step 3: CPU自动压栈完成后,跳转到system_call.s中的_system_call处执行。
<3> 代码路径:kernel/system_call.s
_system_call: cmpl $nr_system_calls-1,%eax ja bad_sys_call push %ds push %es push %fs pushl %edx pushl %ecx # push %ebx,%ecx,%edx as parameters pushl %ebx # to the system call movl $0x10,%edx # set up ds,es to kernel space mov %dx,%ds mov %dx,%es movl $0x17,%edx # fs points to local data space mov %dx,%fs call _sys_call_table(,%eax,4) ………… _sys_fork: call _find_empty_process testl %eax,%eax # 如果返回的是 -EAGAIN(11),说明已有64个进程在运行 js 1f push %gs # 5个push也作为copy_process()的参数初始 pushl %esi pushl %edi pushl %ebp pushl %eax call _copy_process addl $20,%esp 1: ret …………
"int 0x80"软中断是CPU跳转到_system_call处执行,继续将DS、ES、FS、EDX、ECX、EBX压栈(以上一系列的压栈操作都是为了后面调用copy_process函数中初始化进程1中TSS做准备)。
最终,内核通过刚刚设置的寄存器eax的偏移值“2”查询sys_call_table[],得知本次系统调用对应的函数是sys_fork()。(eax为2,可以看成call (sys_call_table + 2 * 4)就是_sys_fork的入口。4的意思是_sys_call_table[]的每一项有4字节,相当于_sys_call_table[2]。)(汇编中对应C语言的函数名在前面多加一个下划线“_”,如C语言的sys_fork对应汇编的就是_sys_fork。)
sys_fork函数:
step 1: 调用find_empty_process()函数 --- 为进程1申请一个空闲位置并获取进程号
step 2: 在进程0的内核栈中继续压栈,将5个寄存器值进栈,为调用copy_process()函数准备参数,这些数据也用来初始化进程1。(最后压栈的eax寄存器的值就是find_empty_process()函数返回的任务号,也将是copy_process()函数的第一个参数int nr。)
step 3: 压栈结束后,调用copy_process()函数
<附1>代码路径:include/linux/sys.h
extern int sys_fork(); fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, …………
<附2>代码路径:kernel/fork.c
// 在task[64]中为进程1申请一个空闲位置并获取进程号 // 内核用全局变量last_pid来存放系统自开机以来累计的进程数,也将此变量用作新建进程的进程号。 // 内核第一次遍历task[64],"&&"条件成立则说明last_pid已被使用,则++last_pid, 直到获得用于新进程的进程号。 // 第二次遍历task[64],获得第一个空闲的i,俗称任务号 // Linux 0.11的task[64]只有64项,最多只能同时运行64个进程 int find_empty_process(void) { int i; repeat: if ((++last_pid)<0) last_pid=1; // 若++后last_pid溢出,则置1 for(i=0 ; i<NR_TASKS ; i++) // 找到有效的last_pid if (task[i] && task[i]->pid == last_pid) goto repeat; for(i=1 ; i<NR_TASKS ; i++) // 返回第一个空闲的i if (!task[i]) return i; return -EAGAIN; // EAGAIN是11 } /* * 1) 为进程1创建task_struct,将进程0的task_struct的内容复制给进程1 * 2) 为进程1的task_struct、tss做个性化设置 * 3) 为进程1创建第一个页表,将进程0的页表项内容赋给这个页表 * 4) 进程1共享进程0的文件 * 5) 设置进程1的GDT项 * 6) 最后将进程1设置为就绪态,使其可以参与进程间的轮转 */ // 这些参数是int 0x80、system_call、sys_fork多次累积压栈的结果,顺序是完全一致的; // 参数的数值都与压栈时的状态有关 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) { struct task_struct *p; int i; struct file *f; // 在16MB内存的最高端获取一页,强制类型转换的潜台词就是将这个页当task_union用 p = (struct task_struct *) get_free_page(); // 从主内存的末端开始向低地址端递进 if (!p) return -EAGAIN; task[nr] = p; // 此时的nr就是1,潜台词是将这个页面当task_union用 …………