Linux内核源码流水笔记之进程的创建

Linux内核源码流水笔记之进程的创建

基于linux.2.0 源码

内核通进调用do_fork(clone_flags, newsp, &regs);创建一个进程

参数clone_flags表明了子进程要与父进程共享的资源,参数struct pt_regs结构体保存了用户态下进程使用的cpu寄存器信息,在内核态父进程会从do_fork返回到用户态,子进程不从do_fork返回,但父子进程最终会从ret_from_sys_call返回到用户态的同一点继续运行即fork处

 

do_fork()函数

  1. struct task_struct *p = (struct task_struct *) kmalloc(sizeof(*p), GFP_KERNEL);//申请一块内存用于保存子进程的task_struct
  2. unsigned long new_stack = alloc_kernel_stack();//申请一页内存1kb,做为内核栈用于,用户空间调用系统调用,处理信号,陷入内核时使用,指针就是一个整型
  3. int nr = find_empty_process();//从task_struct * task[NR_TASKS=512]数组中找出一个空闲的位置用于存放此进程的task_struct
  4. *p = *current;复制父进程的task_struct,浅拷贝
  5. p->state = TASK_UNINTERRUPTIBLE;设定子进程为不可中断的睡眠状态,因为正在创建,使其不可被调度不可被信号中断,
  6. p->kernel_stack_page = new_stack;把内核栈地址保存到task_struct中
  7. p->pid = get_pid(clone_flags);如是vfork flags & CLONE_PID则父子进程共享pid,返回父进程的pid,反之为进新进程寻找一个可用的进程pid,进程pid是一个有符号int整型,从1开始递增,递增至4294934528再从1开始,0是启动内核的那个进程的pid,1是内核第一个启动的进程init进程的pid,
  8. p->signal = 0;清空从父进程继承过来的信号,linux内核使用一个32位的int表示信号集,每一位表示一个信号,信号范围0-31,向进程发送信号就是置该信号值位为1,使用kill向进程发送信号0可以检测进程是否存在,使用pthread_kill向线程发送信号0可以检测线程是否存在
  9. task[nr] = p;把新进程的task_struct存放到task_struct * task[NR_TASKS=512]数组中
  10.        SET_LINKS(p);  nr_tasks++;把新进程插入到一个以init_task开始的全局链表中已便可随时遍历所有存活的进程,并增加任务数
  11. copy_files(clone_flags, p) 如是vfork clone_flags & CLONE_FILES则增加引用计数,反之为子进程分配一个struct file * fd[NR_OPEN=256]数组拷贝父进程打开的所有文件描述符struct file * fd[NR_OPEN=256]到子进程中,此为浅拷贝,所以父进程与子进程共享父进程打开的所有文件描述符,struct file结构体保存了文件的inode,文件读写偏移量,及操作此inode所对应文件的open,read,write,close等函数,因此inode可以表示任何文件系统中的一个磁盘文件,也可表示一个socket或管道
  12. copy_sighand(clone_flags, p) 如是vfork clone_flags & CLONE_SIGHAND则增加引用计数,反之拷贝父进程注册的信号处理函数,所以子进程会继承父进程注册的信号处理函数
  13. copy_mm(clone_flags, p) 如是vfork clone_flags & CLONE_VM则增加引用计数,把子进程页表指针指向父进程的页表即可,反之拷贝父进程的整个虚拟地址空间。linux内核1.0之前是使用单链表来管理进程的虚拟地址空间,对于频繁访问的虚拟地址,当32,64位系统的到来,伴随着的是虚拟地址空间的爆增,链表的查找变的十分低效。linux2.0中使用红黑树来管理虚拟地址空间,拷贝虚拟地址空间就是为子进程构建一棵红黑树把父进程的各个虚拟地址段节点插入到子进程的树中,同时在此函数中会为子进程申请页表,拷贝父进程的页表到子进程的页表,并标记整个虚拟地址空间为读共享,父子进程以只读的方式共享父进程的整个虚拟地址空间,此为fork的copyOnWrite,当有任何一方对共享空间中的一页内存进行写操作(就算1字节也会)将产生内存写错误然后会调用do_wp_page函数申请一页内存,拷贝此页到新申请的内存中,并把此内存的地址通过虚拟地址找到对应的页表项,放到发起写操作进程的页表项中
  14. copy_thread(nr, clone_flags, usp, p, regs);初始化子进程struct thread_struct tss结构体,设置子进程堆栈信息及各个寄存器的值,在此函数内部会设定子进程的tss.eip = (unsigned long) ret_from_sys_call; eax = 0;当子进程被调度运行时,会从ret_from_sys_call返回0到用户空间,完成fork调用的从子进程返回,设定子进程内核态堆栈指针p->tss.esp0 = p->kernel_stack_page + PAGE_SIZE;
  15. wake_up_process(p);    设置子进程状态为p->state = TASK_RUNNING;并把子进程加入可运行队列,以便可以被schedule调度运行
  16. ++total_forks; 用于计算系统负载时使用
  17. return p->pid;父进程直接从do_fork返回,返回子进程pid,
  18. end

 

子进程与父进程仅共享父进程打开的文件描述符和代码段,对于父进程持有的锁资源,子进程并不会持有,对于用于进程间同步的信号量,文件锁,及通信的共享内存,消息队列子进程会获得这些资源的引用拷贝,可以不用在子进程中重新创建而直接使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章