Linux内核0.11分析三之创建子进程

继main函数开启中断和将进程0从0特权级翻转至3特权级后 main函数就调用fork()函数创建子进程

/main.c
static inline _syscall0(int,fork)//内联函数
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)

void main(void) 
{
    ...
    if (!fork()) {      /* we count on this going ok */
        init();
    }
    for(;;) pause();
}

/unistd.h

#define __NR_setup  0   /* used only by init, to get system going */
#define __NR_exit   1
#define __NR_fork   2
#define __NR_read   3
#define __NR_write  4
#define __NR_open   5
#define __NR_close  6
...

volatile void exit(int status);
volatile void _exit(int status);
int fcntl(int fildes, int cmd, ...);
int fork(void);
int getpid(void);
int getuid(void);
int geteuid(void);
...

#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; \
}

...
/sys.h
extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
...
//sys_fork位于sys_call_table的第三项
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 };
...

所以经过宏定义的代替 扩展可以得到fork函数的代码

int fork(void) 
{ 
long __res; 
__asm__ volatile ("int $0x80" 
    : "=a" (__res)       //将__res赋给eax
    : "0" (__NR_fork));  //将2赋值给eax
if (__res >= 0) //int 0x80中断返回就执行这句
    return (type) __res; 
errno = -__res; 
return -1; 
}

这段嵌入的汇编代码 执行步骤主要是
1.先执行”0” (__NR_fork); 将__NR_fork的值即2 赋值给eax 这个2也就是sys_fork在sys_call_table的偏移量。
2.执行int 0x80产生软中断cpu从3特权级的进程跳转到0特权级内核代码执行。中断会使硬件自动将SS,ESP,EFLAGS,CS,EIP寄存器的数值压入内核栈
3.压栈后跳转到system_call.s中的_system_call处执行

_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) //eax是2 即调用sys_fork
    pushl %eax
    movl _current,%eax
    cmpl $0,state(%eax)        # state
    jne reschedule
    cmpl $0,counter(%eax)      # counter
    je reschedule
ret_from_sys_call:
    movl _current,%eax      # task[0] cannot have signals
    cmpl _task,%eax
    je 3f
    cmpw $0x0f,CS(%esp)        # was old code segment supervisor ?
    jne 3f
    cmpw $0x17,OLDSS(%esp)     # was stack segment = 0x17 ?
    jne 3f
    movl signal(%eax),%ebx
    movl blocked(%eax),%ecx
    notl %ecx
    andl %ebx,%ecx
    bsfl %ecx,%ecx
    je 3f
    btrl %ecx,%ebx
    movl %ebx,signal(%eax)
    incl %ecx
    pushl %ecx
    call _do_signal
    popl %eax
3:  popl %eax
    popl %ebx
    popl %ecx
    popl %edx
    pop %fs
    pop %es
    pop %ds
    iret

调用sys_fork执行

_sys_fork:
    call _find_empty_process
    testl %eax,%eax
    js 1f
    push %gs
    pushl %esi
    pushl %edi
    pushl %ebp
    pushl %eax   //将五个寄存器的值压栈 作为copy_process的参数 
    call _copy_process
    addl $20,%esp
1:  ret
//在task[64]中确定进程1的位置
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++)
            if (task[i] && task[i]->pid == last_pid) goto repeat;
    for(i=1 ; i<NR_TASKS ; i++)
        if (!task[i])
            return i;  //返回第一个空闲的i
    return -EAGAIN;
}

接下来是调用copy_process为子进程创建task_struct 页表之类具体分析代码

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)
{//函数参数是前面int 0x80 system_call,sys_fork压栈的寄存器数据
    struct task_struct *p;
    int i;
    struct file *f;

    p = (struct task_struct *) get_free_page();//申请一个空闲页面
    if (!p)
        return -EAGAIN;
    task[nr] = p;
    *p = *current;  /* NOTE! this doesn't copy the supervisor stack */ //将父进程的task_struct复制给子进程 current是当前进程的指针 指向task_struct
    p->state = TASK_UNINTERRUPTIBLE; //子进程设置为不可中断状态
    p->pid = last_pid;               //开始子进程的个性化设置
    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;  //设置子进程的tss
    p->tss.esp0 = PAGE_SIZE + (long) p;  //esp0是内核栈指针
    p->tss.ss0 = 0x10;  //0x10可以看做 10000 0特权级 gdt 数据段
    p->tss.eip = eip; 
    p->tss.eflags = eflags;
    p->tss.eax = 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);  挂载子进程的ldt
    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++)//将父进程相关文件属性的引用计数加1 表明父子进程共享文件
        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)); //设置GDT中与子进程相关的项 就将子进程的tss和ldt挂接在gdt中
    set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
    p->state = TASK_RUNNING;    /* do this last, just in case */// 设置子进程为就绪态
    return last_pid;
}
unsigned long get_free_page(void) //遍历mem_map[],找到主内存中第一个空闲页面
{
register unsigned long __res asm("ax");

__asm__("std ; repne ; scasb\n\t"
    "jne 1f\n\t"
    "movb $1,1(%%edi)\n\t"
    "sall $12,%%ecx\n\t"
    "addl %2,%%ecx\n\t"
    "movl %%ecx,%%edx\n\t"
    "movl $1024,%%ecx\n\t"
    "leal 4092(%%edx),%%edi\n\t"
    "rep ; stosl\n\t"
    "movl %%edx,%%eax\n"
    "1:"
    :"=a" (__res)
    :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
    "D" (mem_map+PAGING_PAGES-1)
    :"di","cx","dx");
return __res;
}
int copy_mem(int nr,struct task_struct * p)
{
    unsigned long old_data_base,new_data_base,data_limit;
    unsigned long old_code_base,new_code_base,code_limit;

    code_limit=get_limit(0x0f); //0x0f 即1111,代码段,LDT,3特权级
    data_limit=get_limit(0x17);//0x17 即10111,数据段,LDT,3特权级
    old_code_base = get_base(current->ldt[1]);  //父进程的代码段数据段基址
    old_data_base = get_base(current->ldt[2]);
    if (old_data_base != old_code_base)
        panic("We don't support separate I&D");
    if (data_limit < code_limit)
        panic("Bad data_limit");
    new_data_base = new_code_base = nr * 0x4000000;//子进程的代码基址 64MB
    p->start_code = new_code_base;
    set_base(p->ldt[1],new_code_base);//设置子进程的代码段基址
    set_base(p->ldt[2],new_data_base); //设置子进程的数据段基址
    //为进程1创建第一个页表 复制进程0的页表 设置进程一的页目录项(通过页目录项就能找到页表 )
    if (copy_page_tables(old_data_base,new_data_base,data_limit)) { 
        free_page_tables(new_data_base,data_limit);
        return -ENOMEM;
    }
    return 0;
}
int copy_page_tables(unsigned long from,unsigned long to,long size)
{
    unsigned long * from_page_table;
    unsigned long * to_page_table;
    unsigned long this_page;
    unsigned long * from_dir, * to_dir;
    unsigned long nr;
        //一个页表的管理范围为4MB 二进制是22个1 所以两边必须为0 即4MB的整数倍 来符合分页的要求
    if ((from&0x3fffff) || (to&0x3fffff))
        panic("copy_page_tables called with wrong alignment");
    from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
    to_dir = (unsigned long *) ((to>>20) & 0xffc);
    size = ((unsigned) (size+0x3fffff)) >> 22;
    for( ; size-->0 ; from_dir++,to_dir++) {
        if (1 & *to_dir)
            panic("copy_page_tables: already exist");
        if (!(1 & *from_dir))
            continue;
        /* from_dir是页目录项的地址 高20位的页表的地址 */
        from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
        if (!(to_page_table = (unsigned long *) get_free_page()))
            return -1;  /* Out of memory, see freeing */
        *to_dir = ((unsigned long) to_page_table) | 7;
        nr = (from==0)?0xA0:1024;
        for ( ; nr-- > 0 ; from_page_table++,to_page_table++) { //复制父进程的页表
            this_page = *from_page_table;
            if (!(1 & this_page))
                continue;
            this_page &= ~2; //设置页表项属性 2是010 ~2是101 代表用户 只读 存在
            *to_page_table = this_page;
            if (this_page > LOW_MEM) {  //1MB以下的内核区不参加用户分页管理
                *from_page_table = this_page;
                this_page -= LOW_MEM;
                this_page >>= 12;
                mem_map[this_page]++; //增加引用计数
            }
        }
    }
    invalidate();
    return 0;
}

进程1创建完毕后就返回执行

_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

执行完copy_process就esp清20字节的栈 也就是前面压入的参数值 这里eax的值是copy_process返回的last_pid 即进程1的进程号

_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)
    pushl %eax
    movl _current,%eax   #当前进程是进程0
    cmpl $0,state(%eax)        # state
    jne reschedule       #如果进程0不是就绪态 就进程调度
    cmpl $0,counter(%eax)      # counter
    je reschedule        #如果当前进程是进程0  就跳到后面的3:执行

call _sys_call_table(,%eax,4)执行完后将eax压栈 然后

3:  popl %eax
    popl %ebx
    popl %ecx
    popl %edx
    pop %fs
    pop %es
    pop %ds
    iret

3:这里主要是将压栈的各个寄存器数值还原

最后

int fork(void) 
{ 
long __res; 
__asm__ volatile ("int $0x80" 
    : "=a" (__res)       //将__res赋给eax
    : "0" (__NR_fork));  //将2赋值给eax
if (__res >= 0) //iret后 执行这里 __res就是eax 值是1
    return (type) __res;  //返回1
errno = -__res;    
return -1; 
}
void main(void)
{
    ...
    if (!fork()) {      /* we count on this going ok */  //返回1 所以这里不会执行init()
        init();
    for(;;) pause();                  //所以从这里开始执行   
    ... 
}

这里就创建玩了进程 接下来是进程调度 下节再分析

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值