执行 SAVE_ALL,保存全部寄存器到当前进程内核栈中;
node
函数返回,执行 RESTORE_ALL,恢复保存的寄存器;执行 iret,cpu 切换至用户态;
数据结构
从 eax 中取出返回值,fork() 返回;函数
详见:系统调用的工做机制atom
当咱们调用 fork()、clone()、vfork() 时,实际上在内核中调用的都是同一个函数 —— do_fork()操作系统
这里的三个系统调用的区别就在于调用 do_fork() 时传入的参数不一样线程
do_fork() 中第一个参数 clone_flags 是一个 32bit 的标志,其中不一样的 bit 置 1 表明不一样的选项,表示新的子进程与父进程之间共享哪些资源3d
其中 sys_fork() 调用 do_fork() 只设置了 SIGCHLD 选项,sys_vfork() 设置了 CLONE_VM | CLONE_VFORK | SIGCHLD 选项,而 sys_clone() 的参数来自上层,经过 ebx 传入;指针
下面简述下 do_fork() 的执行过程code
struct task_struct *tsk; struct thread_info *ti;
tsk = alloc_task_struct(); if (!tsk) return NULL;
struct task_struct { struct thread_info * thread_info; // 指向 thread_info 的指针 struct mm_struct * mm; // 进程地址空间 pid_t pid; struct list_head children; // 子进程链表 ... } struct thread_info { struct task_struct task; // 指向 task_struct 的指针 _u32 cpu; // 当前所在的cpu mm_segment_t addr_limit; // 线程地址空间 // user_thread 0-0xBFFFFFFF // kernel_thread 0-0xFFFFFFFF ... } // thread_info 和 stack 共享一块内存 union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; };
*ti = *current->thread_info; ti->task = tsk;
新进程的进程描述符建立完成,返回至 copy_process()orm
atomic_inc(p->user->process);
检查系统中的进程数量是否超过了 max_threads;
max_threads的数量由系统内存容量决定,全部的thread_info描述符和内核栈所占用空间不能超过系统内存的1/8;
拷贝全部的进程信息:
struct mm_struct { struct vm_area_struct * mmap; // 指向线性区对象的链表头 struct rb_root mm_rb; // 指向线性区对象的红黑树的根 pgd_t * pgd; // 指向页全局目录 atomic_t mm_users; // 次使用计数器,存放共享 mm_struct 数据结构轻量级进程的个数 atomic_t mm_count; // 主使用计数器,每当 mm_count 递减,内核就要检查它是否为0,若是是就要解除这个内存描述符 }
oldmm = current->mm; //oldmm 初始化为父进程的 mm_struct
atomic_inc(&oldmm->mm_users); // 父进程的地址空间引用计数加一 mm = oldmm; // 将父进程地址空间赋给子进程
mm = allocate_mm(); memcpy(mm, oldmm, sizeof(*mm));
struct vm_area_struct { struct mm_struct * vm_mm; // 指向线性区所在的内存描述符 unsigned long vm_start; // 当前线性区起始地址 unsigned long vm_end; // 线性区尾地址 struct vm_area_struct * vm_next; // 下一个线性区 pgprot_t vm_page_prot; // 线性区访问权限 struct rb_node vm_rb; // 用于红黑树搜索的节点 }
新进程的线性区和页表复制完成,返回至copy_process()
调用 copy_thread() 用父进程的内核栈初始化子进程的内核栈
将eax的值强制设为0(fork / clone 系统调用的返回值)
childregs->eax = 0;
进程建立完成,返回至 do_fork()
返回至 do_fork()
fork() 和 vfork() 参数是写死的,而 clone() 是可选的,它能够选择当前建立的进程哪些部分是共享的,哪些部分是独立的;
vfork() 是历史的产物,当调用 fork() 的时候,须要将父进程的线性区和页表都拷贝一份,而调用 exec() 执行新程序后,又要把全部页表删除重置新的页表,创建映射关系,效率很低;
因此要有 vfork(),vfork() 的 clone_flags 位置了 CLONE_VM ,表示共享父进程的地址空间,vfork() 中建立的进程没有分配本身的地址空间,而是经过一个 mm_struct 指针指向父进程的地址空间,这个进程是为了在以后调用 exec() 执行新的程序;
而在有了 Copy-on-write 技术后,fork() 出的子进程只建立了本身的地址空间,而后用父进程的地址空间初始化,每一个页表的项置为父进程的页表项,共享父进程的物理页面,并将全部 私有/可写 页面改成只读;
当咱们改变父子进程的数据后,cpu 在运行过程当中会发生一个缺页错误,cpu 转交控制权给操做系统,操做系统查找 VMA 发现该页权限为只读,但所在段又是可写的,产生一个矛盾,这就是识别 Copy-on-write 的方法,接着 OS 给子进程分配一个新的物理页,并将页表该页的地址修改为新的物理页地址;
这样 fork() 后再调用 exec() 就不用那么麻烦了,能够直接将新的物理页与子进程的虚拟空间创建映射
综上,fork 在建立子进程时,主要作了这些工做