fork源码剖析

2019-07-14 12:39发布

class="markdown_views prism-atom-one-light">
  • 进程
1.什么是进程?
进程可以理解为正在执行的程序。进程控制块(PCB)有操作系统创建和管理。进程控制块是操作系统能够支持多进程和提供多处理的关键工具。
2.进程的创建:1)分配PCB 2)分配地址空间
  • fork系统调用
1.fork()创建新进程 #include #include pid_t fork(void); Fork()函数的每次调用都返回两次,在父进程中返回的时子进程的PID,在子进程中则返回为0。该返回值用于判断是父进程还是子进程。Fork调用失败时返回-1,并设置errno。
2.父进程调用fork()函数创建子进程过程中,子进程的PCB拷贝父进程的PCB,父进程的数据也会复制到子进程中。数据的复制采用写时复制,即在任一进程对数据执行写操作时,先产生缺页中断,然后操作系统给子进程分配内存并复制父进程的数据。创建子进程后,父进程中打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数+1。父进程的用户根目录、当前工作目录等变量的引用计数均会+1.
3.fork() vfork() clone()都是系统调用,他们的底层都是do_fork() 函数调用,只是参数不同
  • 源码剖析
1.转到do_fork()定义
(1)定义一个PCB指针struct task_struct *p;
(2)申请一个pid
在这里插入图片描述
pid是一个整型变量,最大值不会超过整型所表示的最大范围,在2.6内核中pid自身有限制默认为32768.
(3)复制进程描述符copy_process()
在这里插入图片描述
1)子进程获取进程描述符dup_task_struct()
在这里插入图片描述
$1、alloc_task_struct宏为新进程获取进程描述符,并将描述符保存到tsk局部变量中
在这里插入图片描述
$2、alloc_thread_info宏获取一块空闲内存区 /* alloc_thread_info宏获取一块空闲内存区,用来存放新进程的thread_info结构和内核栈 这块内存区字段的大小是8KB或者4KB。 */ ti = alloc_thread_info(tsk); if (!ti) { free_task_struct(tsk); return NULL; } 在这里插入图片描述
2)复制文件描述符copy_files(),内存描述符copy_mm() / * 复制进程文件描述符 */ static int copy_files(unsigned long clone_flags, struct task_struct * tsk) { struct files_struct *oldf, *newf; struct file **old_fds, **new_fds; int open_files, size, i, error = 0, expand; /* * A background process may not have any files ... */ oldf = current->files; if (!oldf) goto out; if (clone_flags & CLONE_FILES) { atomic_inc(&oldf->count); goto out; } ... } /* 当创建一个新的进程时,内核调用copy_mm函数, 这个函数通过建立新进程的所有页表和内存描述符来创建进程的地址空间。 通常,每个进程都有自己的地址空间,但是轻量级进程共享同一地址空间,即允许它们对同一组页进行寻址。 */ static int copy_mm(unsigned long clone_flags, struct task_struct * tsk) { struct mm_struct * mm, *oldmm; int retval; ... } struct mm _struct * mm 内存描述符,描述进程地址的全部信息
3)拷贝线程copy_thread()调用copy_thread,用发出clone系统调用时CPU寄存器的值(它们保存在父进程的内核栈中)
copy_thread()用父进程的现场信息初始化子进程的现场信息,把eax寄存器对应字段的值强制置为0,这是子进程返回值为0的原因。 /* 调用copy_thread,用发出clone系统调用时CPU寄存器的值(它们保存在父进程的内核栈中) 来初始化子进程的内核栈。不过,copy_thread把eax寄存器对应字段的值(这是fork和clone系统调用在子进程中的 返回值) 强行置为0。子进程描述符的thread.esp字段初始化为子进程内核栈的基地址。ret_from_fork的地址存放在thread.eip中。 如果父进程使用IO权限位图。则子进程获取该位图的一个拷贝。 最后,如果CLONE_SETTLS标志被置位,则子进程获取由CLONE系统调用的参数tls指向的用户态数据结构所表示的TLS段。 */ retval = copy_thread(0, clone_flags, stack_start, stack_size, p, regs); if (retval) goto bad_fork_cleanup_namespace; 4)状态置为就绪,父进程时间片分子进程一半,防止通过程序通过fork恶意占用CPU资源 /* 调用sched_fork完成对新进程调度程序数据结构的初始化。 该函数把新进程的状态置为TASK_RUNNING,并把thread_info结构的preempt_count字段设置为1。 从而禁止抢占。 此外,为了保证公平调度,父子进程共享父进程的时间片。 */ sched_fork(p);