对linux 0.11版本内核中进程创建fork()的理解

来源:互联网 发布:你好万维网 知乎 编辑:程序博客网 时间:2024/05/19 08:03

        首先来看一下fork的系统调用,源码如下:

_sys_fork:call _find_empty_process  #这个函数用来取得一个pid,假如是负数直接退出testl %eax,%eax     js 1fpush %gspushl %esipushl %edipushl %ebppushl %eaxcall _copy_process   #这个函数用来复制进程addl $20,%esp       #前面压了5个参数,每个4字节,共20字节,这里的意思就是丢弃这些压栈的值1:ret
        总的来说,看到这里我觉得大致能想到过程应该是取得一个pid,然后复制修改父进程的进程描述符,再复制页表,设置掉GDT,LDT。find_empty_process()源码如下:
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不断增一,然后判断该进程号有没有被任何进程使用过,有的话重复该过程,没有的话继续执行,再找到一个空闲的task项,返回。其中last_pid是一个全局变量,记录当前能分配的pid,不用返回。copy_process()的代码比较长,就直接在代码中利用注释去解释,下面是copy_process()的源码:

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;/*利用get_free_page()申请一页空间也就是4K,这块内存用来存储进程描述符,nr就是前面的函数返回的空闲的任务号,将这个任务号指向新分配的进程描述符,再将当前的任务描述符的内容复制到新分配的内存中,现在子进程的描述符与父进程的完全相同*/p = (struct task_struct *) get_free_page();if (!p)return -EAGAIN;task[nr] = p;*p = *current;/* NOTE! this doesn't copy the supervisor stack *//*接下来就要修改下子进程描述符的内容了*/p->state = TASK_UNINTERRUPTIBLE; //先置为不可中断休眠,避免不小心切换进来p->pid = last_pid;                   //获得子进程的pid p->father = current->pid;             //父进程为当前进程 p->counter = p->priority;             //时间片为优先级的值p->signal = 0;                      //清空信号位图p->alarm = 0;                      //清空报警定时p->leader = 0;                 //会话的领导地位不能与父进程相同p->utime = p->stime = 0;            //一些统计信息清零,用户态和内核态时间p->cutime = p->cstime = 0;          //子进程用户态和内核态时间p->start_time = jiffies;              //设置子进程开始运行的时间/*子进程的tss段内容有与父进程不想听的地方,需要修改*/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;       //子进程的返回值为0p->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(nr)就是找出该任务号在GDT中的位置,也就是选择符,GDT的结构在之前的一篇博文中提到过p->tss.trace_bitmap = 0x80000000;if (last_task_used_math == current) //对协处理器了解较少,这里大概的意思就是父进程使用过了协处理器,就要保存上下文__asm__("clts ; fnsave %0"::"m" (p->tss.i387));/*接下来要调用copy_mem()函数来重新设置子进程代码段和数据段,并复制页表,具体代码后面来分析*/if (copy_mem(nr,p)) {     task[nr] = NULL;free_page((long) p);return -EAGAIN;} /*假如文件时打开的,那该文件的系统文件打开表增一,父子进程能共享这些文件打开表,另外pwd等等这些引用都要增一*/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++;/*gdt的前四个分别是null,内核代码段,内核数据段,系统调用段,加上FIRST_TSS_ENTRY 就是跳过前面四个段,接下来就是任务0的TSS段和LDT段,任务1的TSS和LDT,以此类推,所以加上(nr<<1)就是指向了第nr个任务在GDT中的内容,第一句话设置了该任务的tss在GDT中的描述符,第二句设置了ldt在GDT的描述符,之后使任务变为可运行,然后退出*/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;}
         接下来看一下copy_mem()的源码,如下:

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;/*0x0f是LDT中代码段的选择符,0x17是数据段的描述符,通过选择符来找到描述符进而找到段限长*/code_limit=get_limit(0x0f);0x0f是代码段的选择符,通过选择符data_limit=get_limit(0x17);/*ldt[1]指的是代码段的描述符,ldt[2]指的是数据段的描述符,通过描述符得到段基址*/old_code_base = get_base(current->ldt[1]);old_data_base = get_base(current->ldt[2]);/*linux 0.11里面数据段和代码段不分开,也就是基址和限长相同,不相同就是出错了 */if (old_data_base != old_code_base)panic("We don't support separate I&D");if (data_limit < code_limit)panic("Bad data_limit");/*0x4000000代表一个64M的空间,每个任务在线性地址空间中占据64M,其起始地址就是nr * 0x4000000,这个地址也就是子进程的段基址 */new_data_base = new_code_base = nr * 0x4000000;p->start_code = new_code_base;set_base(p->ldt[1],new_code_base);set_base(p->ldt[2],new_data_base);/*之后拷贝父进程中的页表项,使得父子进程共享*/if (copy_page_tables(old_data_base,new_data_base,data_limit)) {free_page_tables(new_data_base,data_limit);return -ENOMEM;}return 0;}
          总结下fork()的整个流程,一开始要得到一个pid和空闲的任务号,再调用copy_process来创建子进程任务结构的空间,拷贝父进程的内容到这块新申请的空间中,之后要修改进程描述符中的内容,包括pid,统计信息,信号量等,还要修改子进程的tss段指向内容的地址,包括里面的eax,内核堆栈指针,gdt中对子进程ldt的索引号。之后要设置子进程LDT中子进程代码段和数据段的基址和限长,同时拷贝父进程有关代码段数据段的页表,再对父进程中打开的文件表增一等,最后设置gdt中对子进程tss和ldt的描述符。



0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 水原梨花母友人中文字幕 屁孩睡走叔母3中文字幕 林氏集团和伯母 太监跟娘娘私通小说 欧阳锋跟他大嫂私通在哪一集 太监跟娘娘私通 我和唐伯母在厨房 同居了大嫂中文字幕 年轻的后妈1完整高清免费观看 漂亮的老婆在线中字幕 母亲今晚让你入个够免费阅读 漂亮的续母韩国电影 我的漂亮后妈201电影 朋友的母亲3字幕中文翻译 邻居喝醉我睡了他老婆 白天当儿子晚上当丈夫视频 二姑装睡让我 白天是儿子晚上是丈夫阅读 老公去世和儿儿子做 白天是我妈晚上是老婆 蜜母耻中文字幕下载 友人之母中文字幕视频 友人之母增尾彩 内田春菊友人之母 母友人佐佐木明希中文字幕 友人之母谷源希美西瓜 母友人三浦中文字幕在线播放 大嫂被逼无奈和小叔传种接代 母友人作品封面 6公公与儿熄中文字幕完整视频 退休闲负的公公在家要了我5 我妻子的母亲hd高清中文字幕 叔母到我公寓在线播放 叔母来探望生病的我 叔父叔母来我家住数 请假在朋友妈猛烈中文字幕 母亲的朋友 谷原西美 播放小朋搞朋友母亲中文字在线 母亲比妻子好中文字幕大桥瞳 一道不卡免费播放二区 一道不卡免费播放