zoukankan      html  css  js  c++  java
  • UNIX环境高级编程学习笔记(十)为何 fork 函数会有两个不同的返回值【转】

    转自:http://blog.csdn.net/fool_duck/article/details/46917377

    以下是基于 linux 0.11 内核的说明。

    在init/main.c第138行,
    在move_to_user_mode()之后,进程0通过fork()产生子进程,实际就是进程1(init进程)。

    在main.c第23行:

    static inline _syscall0(int,fork)

     

    通过 _syscall0 调用 fork 。_syscall0 即不带参数的系统调用:type name(void),_syscall0 的定义在unistd.h中第133行:

    #define _syscall0(type,name) 
    type name(void) 
    { 
    long __res; 
    __asm__ volatile ("int $0x80"           //调用0x80系统中断
        : "=a" (__res) 
        : "0" (__NR_##name)); 
    if (__res >= 0) 
        return (type) __res; 
    errno = -__res; 
    return -1; 
    }

    在 kernelsched.c 中的 sched_init 调用 system_call

    
    void sched_init(void)
    {
        int i;
        struct desc_struct * p;
    
        if (sizeof(struct sigaction) != 16)
            panic("Struct sigaction MUST be 16 bytes");
        set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
        set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
        p = gdt+2+FIRST_TSS_ENTRY;
        for(i=1;i<NR_TASKS;i++) {
            task[i] = NULL;
            p->a=p->b=0;
            p++;
            p->a=p->b=0;
            p++;
        }
    /* Clear NT, so that we won't have troubles with that later on */
        __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
        ltr(0);
        lldt(0);
        outb_p(0x36,0x43);      /* binary, mode 3, LSB/MSB, ch 0 */
        outb_p(LATCH & 0xff , 0x40);    /* LSB */
        outb(LATCH >> 8 , 0x40);    /* MSB */
        set_intr_gate(0x20,&timer_interrupt);
        outb(inb_p(0x21)&~0x01,0x21);
        set_system_gate(0x80,&system_call);
    }
    

    system_call 位于 kernelsystem_call.s 中。在该文件第94行:

    call _sys_call_table(,%eax,4)
    • 1

    调用地址为:_sys_call_table + %eax * 4,此时 exa 的值为2(根据__NR_fork的定义),由于是32位机,指针占4个字节。

    sys_call_table 的定义在 includelinuxsys.h 中第74行:

    fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
    sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
    sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
    sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
    sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
    sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
    sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
    sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
    sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
    sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
    sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
    sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
    sys_setreuid,sys_setregid };

    即调用的是 sys_fork 。

    sys_fork 的定义是一段汇编代码,位于 kernelsystem_call.s 第208行:

    _sys_fork:
        call _find_empty_process
        testl %eax,%eax
        js 1f
        push %gs
        pushl %esi
        pushl %edi
        pushl %ebp
        pushl %eax
        call _copy_process
        addl $20,%esp
    1:  ret
    

    在 sys_fork 中,有两个主要的函数调用:_find_empty_process 和 _copy_process 。

    _find_empty_process 位于 kernelfork.c 中第135行,其作用是找到一个空的进程号(对应于一个进程控制块PCB),在linux 0.11版本中最多支持64个进程(全局数组task定义在sched.h中,数组大小为64):

    int find_empty_process(void)
    {
        int i;
    
        repeat:
            if ((++last_pid)<0) last_pid=1;
            for(i=0 ; i<NR_TASKS ; i++)
                if (task[i] && task[i]->pid == last_pid) goto repeat;
        for(i=1 ; i<NR_TASKS ; i++)
            if (!task[i])
                return i;
        return -EAGAIN;
    }
    

    全局变量last_pid用来记录上次使用的进程号,其定义在 kernelfork.c 第22行:

        long last_pid=0;
    • 1

    在find_empty_process中,不断递增last_pid,寻找第一个未被其它进程使用的进程号作为新进程的进程号。如果递增后的值超出正数表示范围,则重新从1开始,并将其返回值存放在 %eax 中。若没能找到可用进程号,则跳转。若找到可用进程号则进行相关压栈操作,然后调用_copy_process 开始复制进程内容。

    _copy_process 的定义位于 kernelfork.c 第63行:

    /*
     *  Ok, this is the main fork-routine. It copies the system process
     * information (task[nr]) and sets up the necessary registers. It
     * also copies the data segment in it's entirety.
     */
    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;
    
        p = (struct task_struct *) get_free_page();    //获为新任务分配内存
        if (!p)
            return -EAGAIN;
        task[nr] = p;       //将新任务结构指针放入任务数组中,其中nr 是由前面find_empty_process()返回的任务号
        *p = *current;  /* NOTE! this doesn't copy the supervisor stack */
        p->state = TASK_UNINTERRUPTIBLE;    // 将新进程的状态先置为不可中断等待状态
        p->pid = last_pid;     // fork 对父进程返回子进程ID
        p->father = current->pid;
        p->counter = p->priority;
        p->signal = 0;
        p->alarm = 0;
        p->leader = 0;      /* process leadership doesn't inherit */
        p->utime = p->stime = 0;
        p->cutime = p->cstime = 0;
        p->start_time = jiffies;
        p->tss.back_link = 0;
        p->tss.esp0 = PAGE_SIZE + (long) p;
        p->tss.ss0 = 0x10;
        p->tss.eip = eip;
        p->tss.eflags = eflags;
        p->tss.eax = 0;     // fork 对子进程返回0
        p->tss.ecx = ecx;
        p->tss.edx = edx;
        p->tss.ebx = ebx;
        p->tss.esp = esp;
        p->tss.ebp = ebp;
        p->tss.esi = esi;
        p->tss.edi = edi;
        p->tss.es = es & 0xffff;
        p->tss.cs = cs & 0xffff;
        p->tss.ss = ss & 0xffff;
        p->tss.ds = ds & 0xffff;
        p->tss.fs = fs & 0xffff;
        p->tss.gs = gs & 0xffff;
        p->tss.ldt = _LDT(nr);
        p->tss.trace_bitmap = 0x80000000;
        if (last_task_used_math == current)
            __asm__("clts ; fnsave %0"::"m" (p->tss.i387));
        if (copy_mem(nr,p)) {
            task[nr] = NULL;
            free_page((long) p);
            return -EAGAIN;
        }
        for (i=0; i<NR_OPEN;i++)
            if (f=p->filp[i])
                f->f_count++;
        if (current->pwd)
            current->pwd->i_count++;
        if (current->root)
            current->root->i_count++;
        if (current->executable)
            current->executable->i_count++;
        set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
        set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
        p->state = TASK_RUNNING;    /* do this last, just in case */
        return last_pid;
    }

    进程控制块中还保存有进程的任务状态段数据结构tss,用于存储处理器管理进程的所有信息。也就是说,在任务切换过程中,首先将处理器中各寄存器的当前值被自动保存当前进程的tss中;然后,下一进程的tss被加载并从中提取出各个值送到处理器的寄存器中。由此可见,通过在tss中保存任务现场各寄存器状态的完整映象,实现任务的切换。

        struct tss_struct tss;

    因此,一旦在task[]数组中找到空闲项和进程号,我们就可以为该进程的进程控制块结构申请一个页面的内存。这个工作是在copy_process() 函数中完成的。

    当然copy_process()函数的最主要的任务是为子进程复制父进程信息,并设置子进程的任务状态段,其中最关键的两步是:

    • 把子进程tss中的eip设置为父进程系统调用返回地址,这样当子进程被调度程序选中后,将从父进程的fork()返回处开始执行。
        p->tss.eip = eip;

     

    • 把子进程tss中的eax设置为0,而eax是存放函数返回值的地方,这样子进程中返回的是0。注意子进程并没有执行fork()函数,子进程的系统堆栈没有进行过操作,当然不会有像父进程那样的fork函数调用。但是当子进程开始运行时,就好像它从 fork 中返回一样。
        p->tss.eax = 0;
  • 相关阅读:
    Fiddler 教程
    ios iOS手势识别的详细使用(拖动,缩放,旋转,点击,手势依赖,自定义手势)
    ios 生成一个动态的随机的头像/随机数的操作
    在工程中如何使用一个公用的页面
    使用手势,让键盘在点击空白处消失
    ios开发之--iOS 11适配:iOS11导航栏返回偏移
    svn 操作字母的提示
    字面量
    控制 打开和关闭远程推送通知
    常见结构体 日期 字符串的操作 很实用
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/8080501.html
Copyright © 2011-2022 走看看