进程1的创建_&_父子进程创建机制

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




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值