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用
…………