Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2122974
  • 博文数量: 438
  • 博客积分: 3871
  • 博客等级: 中校
  • 技术积分: 6075
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-10 00:11
个人简介

邮箱: wangcong02345@163.com

文章分类

全部博文(438)

文章存档

2017年(15)

2016年(119)

2015年(91)

2014年(62)

2013年(56)

2012年(79)

2011年(16)

分类: LINUX

2016-09-21 17:42:57

1. 在 ./init/main.c 中第一次出现fork调用
  1. static inline _syscall0(int,fork)
  2. void main(void)
  3. {
  4.     move_to_user_mode();  //fork之前,进程的特权级是3,且当前执行的是进程0
  5.     if( !fork() ){        //由进程0去创建进程1 -->调用fork完成这个操作  
  6.         init();
  7.     }
  8. }
2. fork函数的原型及实现
在init/main.c中对有如下形式-->inline _syscall0(int,fork)
其中_syscall0 是一个宏定义:
在 ./include/unistd.h 中
  1. #define _syscall0(type,name) \
  2. type name(void) \
  3. { \
  4. long __res; \
  5. __asm__ volatile ("int $0x80" \
  6.     : "=a" (__res) \
  7.     : "0" (__NR_##name)); \
  8. if (__res >= 0) \
  9.     return (type) __res; \
  10. errno = -__res; \
  11. return -1; \
  12. }


将宏展开后: NR_fork=2
  1. inline int fork(void)
  2. {
  3.     long __res;
  4.     __asm__ volatile ("int $0x80"
  5.         : "=a" (__res)
  6.         : "0" (__NR_fork)); -->2 
  7.     if (__res >= 0)       //这是为什么父进程会返回>0
  8.         return (int) __res;
  9.     errno = -__res;
  10.     return -1;
  11. }


汇编的格式看更方便:
  1. inline _syscall0(int,fork)
  2.     64c0: 83 ec 10         sub $0x10,%esp
  3.     64c3: b8 02 00 00 00   mov $0x2,%eax   -->把NR_fork放在eax
  4.     64c8: cd 80            int $0x80       -->作为int 0x80的参数
  5.     64ca: 89 44 24 0c      mov %eax,0xc(%esp)
  6.     64ce: 83 7c 24 0c 00   cmpl $0x0,0xc(%esp)
  7.     64d3: 78 06            js 64db <fork+0x1b>
  8.     64d5: 8b 44 24 0c      mov 0xc(%esp),%eax
  9.     64d9: eb 10            jmp 64eb <fork+0x2b>
  10.     64db: 8b 44 24 0c      mov 0xc(%esp),%eax
  11.     64df: f7 d8            neg %eax
  12.     64e1: a3 04 1c 04 00   mov %eax,0x41c04
  13.     64e6: b8 ff ff ff ff   mov $0xffffffff,%eax
  14.     64eb: 83 c4 10         add $0x10,%esp
  15.     64ee: c3               ret

















函数是用NR_fork 来初始化eax (eax=2), 说明fork就是int 0x80的 2号调用,进入中断
3. 在 ./kernel/system_call.s 中找到int 0x80中断的处理函数
  1. .align 4
  2. system_call:
  3.     push %ds
  4.     push %es
  5.     push %fs
  6.     pushl %eax        # save the orig_eax
  7.     pushl %edx        
  8.     pushl %ecx        # push %ebx,%ecx,%edx as parameters
  9.     pushl %ebx        # to the system call
  10.     movl $0x10,%edx        # set up ds,es to kernel space
  11.     mov %dx,%ds
  12.     mov %dx,%es
  13.     movl $0x17,%edx        # fs points to local data space
  14.     mov %dx,%fs
  15.     cmpl NR_syscalls,%eax
  16.     jae bad_sys_call
  17.     call sys_call_table(,%eax,4)     -->即调用sys_fork函数
  18.     pushl %eax
  19. 2:
  20.     movl current,%eax
  21.     cmpl $0,state(%eax)          # state
  22.     jne reschedule               -->如果不是就绪状态就重新调度
  23.     cmpl $0,counter(%eax)        # counter
  24.     je reschedule                -->如果时间片为0,就重新调度  
  25. ret_from_sys_call:               -->如果是就绪状态并且时间片不为0
  26. //如果是进程0,则直接返回
  27.     movl current,%eax
  28.     cmpl task,%eax            # task[0] cannot have signals
  29.     je 3f
  30.     cmpw $0x0f,CS(%esp)        # was old code segment supervisor ?
  31.     jne 3f
  32.     cmpw $0x17,OLDSS(%esp)        # was stack segment = 0x17 ?
  33.     jne 3f
  34.     movl signal(%eax),%ebx
  35.     movl blocked(%eax),%ecx
  36.     notl %ecx
  37.     andl %ebx,%ecx
  38.     bsfl %ecx,%ecx
  39.     je 3f
  40.     btrl %ecx,%ebx
  41.     movl %ebx,signal(%eax)
  42.     incl %ecx
  43.     pushl %ecx
  44.     call do_signal
  45.     popl %ecx
  46.     testl %eax, %eax
  47.     jne 2b        # see if we need to switch tasks, or do more signals
  48. 3:    popl %eax
  49.     popl %ebx
  50.     popl %ecx
  51.     popl %edx
  52.     addl $4, %esp    # skip orig_eax
  53.     pop %fs
  54.     pop %es
  55.     pop %ds
  56.     iret
a. 在./include/linux/sched.h 中有 fn_ptr 的定义
    typedef int (*fn_ptr) ()
b. 在 ./include/linux/sys.h 中
    fn_ptr sys_call_table[] = { sys_setup, sys_exit,sys_fork,...}
c. 所以call 的调用地址就是
    _sys_call_table + %eax * 4 即:sys_fork ( 其中eax=2, 每个指针的长度是4, 所以用_sys_call_table的首地址加 2*4, 就是 _sys_call_table 数组中的第二个元素的地址,即sys_fork )
call _sys_call_table(,%eax,4) 即:call _sys_fork
4. sys_fork --> 在./kernel/system_call.s中
  1. .align 2
  2. _sys_fork:
  3.     call _find_empty_process          
  4.     test1 %eax,%eax             //如果eax为负则test后SF=1置位
  5.     js 1f                       //如果符号位置位(SF:负数=1 非负=0)则说明出错跳转
  6.     push %gs
  7.     pushl %esi
  8.     pushl %edi
  9.     pushl %ebp
  10.     pushl %eax                 //给copy_process传eax=last_pid,ebp,edi,esi,gd这5个参数,注意顺序
  11.     call _copy_process
  12.     add $20,%esp
  13. 1: ret
可见fork又主要是调用了两个函数:
    find_empty_process 和 copy_process ;它们都在kernel/fork.c中实现
说明:gcc编译器在生成汇编代码,其函数名及变量名前都会都会加_,所以在汇编中调用C的函數或变量的时候,需要手动加上一个下划线。
4.1 find_empty_process -->在kernel/fork.c中
为新进程分配pid及未使用的task[i]结构体
  1. 144 int find_empty_process(void)
  2. 145 {
  3. 146      int i;
  4. 147
  5. 148 repeat:
  6. //last_pid是一个有符号的long,当last_pid越界后变为负数,把last_pid置1
  7. 149      if ((++last_pid)<0) last_pid=1;
  8. //有了pid号之后,在64个task中一一搜索,检查这个pid号是否被占用
  9. 150      for(i=0 ; i<NR_TASKS ; i++)
  10. 151           if (task[i] && ((task[i]->pid == last_pid) ||  
  11. 152                (task[i]->pgrp == last_pid)))
  12. 153               goto repeat;
  13. //在64个task中选出为还没有使用的一项返回
  14. 154      for(i=1 ; i<NR_TASKS ; i++)
  15. 155           if (!task[i])
  16. 156               return i;       -->若没出错则返回正值,并保存在eax中sys_fork会判断这个值
  17. 157      return -EAGAIN;          -->若出错则返回负值,并保存在eax中sys_fork会判断这个值
  18. 158 }
4.2 copy_process -->  在kernel/fork.c中
  1. /*
  2.  * Ok, this is the main fork-routine. It copies the system process
  3.  * information (task[nr]) and sets up the necessary registers. It
  4.  * also copies the data segment in it's entirety.
  5.  */
  6. int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
  7.         long ebx,long ecx,long edx, long orig_eax,
  8.         long fs,long es,long ds,
  9.         long eip,long cs,long eflags,long esp,long ss)
  10. {
  11.     struct task_struct *p;
  12.     int i;
  13.     struct file *f;
  14. //首先为task_struct分配空间
  15.     p = (struct task_struct *) get_free_page();
  16.     if (!p)
  17.         return -EAGAIN;
  18.     task[nr] = p;          //第1次fork时,nr=1
  19.     //*p = *current;    /* this doesn't copy the supervisor stack */
  20.     memcpy(p, current, sizeof(struct task_struct));
  21.     p->state = TASK_UNINTERRUPTIBLE;
  22.     p->pid = last_pid;
  23.     p->counter = p->priority;
  24.     p->signal = 0;
  25.     p->alarm = 0;
  26.     p->leader = 0;        /* process leadership doesn't inherit */
  27.     p->utime = p->stime = 0;
  28.     p->cutime = p->cstime = 0;
  29.     p->start_time = jiffies;
  30.     p->tss.back_link = 0;
  31.     p->tss.esp0 = PAGE_SIZE + (long) p;
  32.     p->tss.ss0 = 0x10;
  33.     p->tss.eip = eip;
  34.     p->tss.eflags = eflags;
  35.     p->tss.eax = 0;                          //这是为什么子进程会返回0
  36.     p->tss.ecx = ecx;
  37.     p->tss.edx = edx;
  38.     p->tss.ebx = ebx;
  39.     p->tss.esp = esp;
  40.     p->tss.ebp = ebp;
  41.     p->tss.esi = esi;
  42.     p->tss.edi = edi;
  43.     p->tss.es = es & 0xffff;
  44.     p->tss.cs = cs & 0xffff;
  45.     p->tss.ss = ss & 0xffff;
  46.     p->tss.ds = ds & 0xffff;
  47.     p->tss.fs = fs & 0xffff;
  48.     p->tss.gs = gs & 0xffff;
  49.     p->tss.ldt = _LDT(nr);
  50.     p->tss.trace_bitmap = 0x80000000;
  51.     if (last_task_used_math == current)
  52.         __asm__("clts ; fnsave %0 ; frstor %0"::"m" (p->tss.i387));
  53.     if (copy_mem(nr,p)) {
  54.         task[nr] = NULL;
  55.         free_page((long) p);
  56.         return -EAGAIN;
  57.     }
  58.     for (i=0; i<NR_OPEN;i++)
  59.         if (f=p->filp[i])
  60.             f->f_count++;
  61.     if (current->pwd)
  62.         current->pwd->i_count++;
  63.     if (current->root)
  64.         current->root->i_count++;
  65.     if (current->executable)
  66.         current->executable->i_count++;
  67.     if (current->library)
  68.         current->library->i_count++;
  69. //进程0的nr=0,进程1的nr=1,所以这儿是与gdt[6]=tss1, gdt[7]=ldt1
  70.     set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));    
  71.     set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
  72.     p->p_pptr = current;
  73.     p->p_cptr = 0;
  74.     p->p_ysptr = 0;
  75.     p->p_osptr = current->p_cptr;
  76.     if (p->p_osptr)
  77.         p->p_osptr->p_ysptr = p;
  78.     current->p_cptr = p;
  79.     p->state = TASK_RUNNING;    /* do this last, just in case */
  80.     return last_pid;
  81. }
注:要想知道copy_process中的这一堆函数是怎么来的,需要从头开始捋
a. fork的流程
   -->int 0x80中断
   -->进入0x80的中断处理函数system_call
    -->system_call中会调用call sys_call_table 即sys_fork
    -->sys_fork会调用_copy_process
b.上述各个阶段传的参数
  b.1 int 0x80中断时会将ss esp压栈
  b.2 sys_call中给copy_process传ds,es,fs, org_eax,edx,ecx,ebx -->7个
  b.3 sys_call中call sys_call_table(,%eax,4)把下一条eip压栈-->none
  b.4 sys_fork中给copy_process传gs,esi,edi,ebp,eax=last_pid -->5个

  1. int copy_mem(int nr,struct task_struct * p)
  2. {
  3.     unsigned long old_data_base,new_data_base,data_limit;
  4.     unsigned long old_code_base,new_code_base,code_limit;
  5. //取进程0的ldt中代码段与数据段的段限长与段基,在进程0的段基地址上加64M,就是进程1的段基,段界限不变都是640K
  6.     code_limit=get_limit(0x0f);                   -->进程0的代码段界限
  7.     data_limit=get_limit(0x17);                   -->进程0的数据段界限 
  8.     old_code_base = get_base(current->ldt[1]);    -->进程0的代码段基地址
  9.     old_data_base = get_base(current->ldt[2]);    -->进程0的数据段基地址
  10.     if (old_data_base != old_code_base)
  11.         panic("We don't support separate I&D");
  12.     if (data_limit < code_limit)
  13.         panic("Bad data_limit");
  14.     new_data_base = new_code_base = nr * TASK_SIZE-->进程1的LDT的代码段与数据段基地址=进程0的基地址+64M
  15.     p->start_code = new_code_base;
  16.     set_base(p->ldt[1],new_code_base);              -->设置进程1的LDT的代码段与数据段基地址
  17.     set_base(p->ldt[2],new_data_base);
  18. //
  19.     if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
  20.         free_page_tables(new_data_base,data_limit);
  21.         return -ENOMEM;
  22.     }
  23.     return 0;
  24. }




  1. //这个函数只会被fork调用 
  2. int copy_page_tables(unsigned long from,unsigned long to,long size)
  3. {
  4.     unsigned long * from_page_table;
  5.     unsigned long * to_page_table;
  6.     unsigned long this_page;
  7.     unsigned long * from_dir, * to_dir;
  8.     unsigned long new_page;
  9.     unsigned long nr;
  10. //检查边界是不是4M对齐,from和to都是进程的基地址,进程的基地址都是以64M为单位的加减的。
  11.     if ((from&0x3fffff) || (to&0x3fffff))
  12.         panic("copy_page_tables called with wrong alignment");
  13. //取from与to的页目录表项地址,及size占了几个页目录表项
  14.     from_dir = (unsigned long *) ((from>>20) & 0xffc);  
  15.     to_dir = (unsigned long *) ((to>>20) & 0xffc);
  16.     size = ((unsigned) (size+0x3fffff)) >> 22;        -->一个页目录表项可以映射4M内存,这个size是以4M为单位
  17.     for( ; size-->0 ; from_dir++,to_dir++) {
  18. //检查P位,若dst的P位存在,说明目的内存己占用,出错; 若src的P位不存在则,要复制的内存不存在,出错。
  19.         if (1 & *to_dir)
  20.             panic("copy_page_tables: already exist");
  21.         if (!(1 & *from_dir))
  22.             continue;
  23.         from_page_table = (unsigned long *) (0xfffff000 & *from_dir);   //取页目录表所映射的页表地址
  24.         if (!(to_page_table = (unsigned long *) get_free_page()))       //为dst的页目录表要映射的页表分配内存
  25.             return -1;    /* Out of memory, see freeing */
  26.         *to_dir = ((unsigned long) to_page_table) | 7;                   //设置页目录表项的存在标志
  27.         nr = (from==0)?0xA0:1024;
  28.         for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
  29.             this_page = *from_page_table;
  30.             if (!this_page)
  31.                 continue;
  32.             if (!(1 & this_page)) {
  33.                 if (!(new_page = get_free_page()))
  34.                     return -1;
  35.                 read_swap_page(this_page>>1, (char *) new_page);
  36.                 *to_page_table = this_page;
  37.                 *from_page_table = new_page | (PAGE_DIRTY | 7);
  38.                 continue;
  39.             }
  40.             this_page &= ~2;
  41.             *to_page_table = this_page;
  42.             if (this_page > LOW_MEM) {
  43.                 *from_page_table = this_page;
  44.                 this_page -= LOW_MEM;
  45.                 this_page >>= 12;
  46.                 mem_map[this_page]++;
  47.             }
  48.         }
  49.     }
  50.     invalidate();
  51.     return 0;
  52. }



4.3 set_ldt_desc --> 在include/asm/system.h中
在gdt[n+常数]处嵌入ldt,将参数addr填到基地址中
  1. #define _set_tssldt_desc(n,addr,0x82)
  2. __asm__ ("movw $104,%1"   -->BYTE[0-1]:TSS与LDT段限长=104
  3.     "movw %%ax,%2"        -->BYTE[2-3]:段基址[0-15]
  4.     "rorl $16,%%eax"      -->将eax的高16位与低16位交换
  5.     "movb %%al,%3"        -->BYTE[4]: al是段基址的[16-23]
  6.     "movb $0x82,%4"       -->BYTE[5]: P=1 DPL=0 S=0 TYPE=2(数据段) TYPE=9(代码段)
  7.     "movb $0x00,%5"       -->BYTE[6]: 置0 G=0,DB=0,AVL=0
  8.     "movb %%ah,%6"        -->BYTE[7]: ah是段基址的[24-31]
  9.     "rorl $16,%%eax"      -->将eax的高16位与低16位再交换一次,即把eax恢复成原值
  10.     ::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)),
  11.      "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7))
  12.     )


二. 几个问题
2.1 int 0x80与system_call是如何关联起来的?--> 在kernel/sched.c中
sched_init
    --> set_system_gate(0x80,&system_call);
    -->_set_gate(&idt[n],15,3,addr)    //type=15=0xF=1111B说明是一个陷阱门
  1. #define _set_gate(gate_addr,type,dpl,addr) \
  2. __asm__ ("movw %%dx,%%ax\n\t" \          -->edx是addr的全部地址,这儿只用了低16位
  3. "movw %0,%%dx\n\t" \
  4. "movl %%eax,%1\n\t" \                    -->eax的低16位是addr的低16位,eax的高16位是选择子
  5. "movl %%edx,%2" \                        -->edx的低16位是属性,edx的高16位是addr的高16位
  6. : \                                      -->movl一次移动4个字节,这样既填充了属性又填充了addr
  7. : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ -->0x8000-->P=1 dpl偏移是13,type偏移是8
  8. "o" (*((char *) (gate_addr))), \
  9. "o" (*(4+(char *) (gate_addr))), \
  10. "d" ((char *) (addr)),"a" (0x00080000))
陷阱门 -->不会关中断-->可以嵌套
中断门 -->会关中断  -->不可以嵌套

2.2 fork后父子进程的返回值为什么不一样?
fork是在c语言中调用的,c语言中的返回值都是在eax中,
父进程: 返回值是int 0x80调用的返回值,system_call-->sys_fork-->copy_process的返回值,即last_pid也就是子进程的pid.
子进程:在copy_process中设置p->tss.eax=0,当任务调度执行到子进程时,eax会被tss中的eax覆盖,则fork后返回0.

附录1. test影响SF位
  1. mov eax, 1
  2. test eax, eax

  3. mov eax, -1
  4. test eax, eax
下面是bochs的调试结果:
  1. Next at t=156838332
  2. (0) [0x000000001718] 000f:00001718 (unk. ctxt): mov eax, 0x00000001 ; b801000000
  3. <bochs:8> n
  4. Next at t=156838333
  5. (0) [0x00000000171d] 000f:0000171d (unk. ctxt): test eax, eax ; 85c0
  6. <bochs:9> r
  7. eflags 0x00000286: id vip vif ac vm rf nt IOPL=0 of df IF tf SF zf af PF cf -->原先SF=1是置位的
  8. <bochs:10> n
  9. Next at t=156838334
  10. (0) [0x00000000171f] 000f:0000171f (unk. ctxt): mov eax, 0xffffffff ; b8ffffffff
  11. <bochs:11> r
  12. eflags 0x00000202: id vip vif ac vm rf nt IOPL=0 of df IF tf sf zf af pf cf    -->eax=1,执行test后,sf=0
  13. <bochs:12> n
  14. Next at t=156838335
  15. (0) [0x000000001724] 000f:00001724 (unk. ctxt): test eax, eax ; 85c0          
  16. <bochs:13> r
  17. eflags 0x00000202: id vip vif ac vm rf nt IOPL=0 of df IF tf sf zf af pf cf    
  18. <bochs:14> n
  19. Next at t=156838336
  20. (0) [0x000000001726] 000f:00001726 (unk. ctxt): mov al, 0x46 ; b046
  21. <bochs:15> r
  22. eflags 0x00000286: id vip vif ac vm rf nt IOPL=0 of df IF tf SF zf af PF cf  -->eax=-1,执行test后,SF=1



阅读(829) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~