进程不仅局限于一段可执行代码,包含了进程的其他资源,例如打开的文件、挂起的信号量、内存管理、处理器状态、一个/多个执行线程和数据段。线程被称作轻量级进程,它是操作系统调度的最小单元,通常一个进程可以拥有多个线程。进程拥有独立的资源空间,线程可以共享进程中的资源空间。内核使用kernel_clone方式创建线程,和创建进程clone类似,会确定哪些资源与父进程共享,哪些是线程独占的资源。
进程通过fork调用来创建一个新的进程,新创建的进程可以通过exec创建新的地址空间,并载入新的程序。除idle进程外,所有进程都是由clone系统创建。
init进程的创建
内核启动时会有一个init_task进程,它是系统的0号进程/idle进程。后续所有的进程都是由它clone出来的。
start_kernel函数中set_task_stack_end_magic会初始化init_stack结构体的栈边界地址,每一个进程创建的时候,系统会为这个进程创建2个页大小的内核栈。
init_task是静态定义的一个进程,也就是说当内核被放入内存时,它就已经存在,它没有自己的用户空间,一直处于内核空间中运行,并且也只处于内核空间运行。0号进程用于包括内存、页表、必要数据结构、信号量、调度器、硬件设备等的初始化。当它执行到最后(剩余初始化)时,start_kernel中所有的初始化执行完成后,会在内核中启动一个kernel_init内核线程和一个kthreadd内核线程,kernel_init内核线程执行到最后会通过execve系统调用执行转变为我们所熟悉的init进程,而kthreadd内核线程是内核用于管理调度其他的内核线程的守护线程。在最后init_task将变成一个idle进程,用于在CPU没有进程运行时运行它,它在此时处于空载状态。
do_fork已经在最新的内核代码中被删除。取而代之的是kernel_clone创建新进程。因此glibc中的fork函数会调用kernel_clone。
三种进程创建机制:
- fork()是将父进程的全部资源拷贝给子进程;
- vfork()是将父进程除mm_struct的所有资源拷贝给子进程;
- clone()是通过CLONE_XXX指定将哪些资源从父进程拷贝到子进程
fork
- 使用fork()函数创建子进程时,子进程和父进程有各自独立的进程地址空间,fork后会重新申请一份资源,包括进程描述符、进程上下文、进程堆栈、内存信息、打开的文件描述符、进程优先级、根目录、资源限制、控制终端等,拷贝给子进程。
- fork函数会返回两次,一次在父进程,另一次在子进程,如果返回值为0,说明是子进程;如果返回值为正数,说明是父进程
- fork系统调用只使用SIGCHLD标志位,子进程终止后发送SIGCHLD信号通知父进程;
- fork是重量级调用,为子进程创建了一个基于父进程的完整副本,然后子进程基于此运行,为了减少工作量采用写时拷贝技术。子进程只复制父进程的页表,不会复制页面内容,页表的权限为RD-ONLY。当子进程需要写入新内容时会触发写时复制机制,为子进程创建一个副本,并将页表权限修改为RW。
- 由于需要修改页表,触发page fault等,因此fork需要mmu的支持。
vfork
- 使用vfork()函数创建子进程时, 子进程和父进程有相同的进程地址空间,vfork会将父进程除mm_struct的资源拷贝给子进程,也就是创建子进程时,它的task_struct->mm指向父进程的,父子进程共享一份同样的mm_struct;
- vfork会阻塞父进程,直到子进程退出或调用exec释放虚拟内存资源,父进程才会继续执行;
- vfork的实现比fork多了两个标志位,分别是CLONE_VFORK和CLONE_VM。CLONE_VFORK表示父进程会被挂起,直至子进程释放虚拟内存资源。CLONE_VM表示父子进程运行在相同的内存空间中;
- 由于没有写时拷贝,不需要页表管理,因此vfork不需要MMU
clone
- 使用clone()创建用户线程时, clone不会申请新的资源,所有线程指向相同的资源,举例:P1创建P2,P2的全部资源指针指向P1,P1和P2指向同样的资源,那么P1和P2就是线程;
- 当调用pthread_create时,linux就会执行clone,并通过不同的clone_flags标记,保证p2指向p1相同的资源。
- 创建进程和创建线程采用同样的api即kernel_clone,带有标记clone_filag可以指明哪些是要克隆的,哪些不需要克隆的;
- 进程是完全不共享父进程资源,线程是完全共享父进程的资源,通过clone_flags标志克隆父进程一部分资源,部分资源与父进程共享,部分资源与父进程不共享,是位于进程和线程间的临界态
kernel_clone
kernel_clone(struct kernel_clone_args *args)
|--u64 clone_flags = args->flags;
| struct completion vfork;
| struct task_struct *p;
| int trace = 0;
|--检查子进程是否允许被跟踪
| //创建一个进程并返回task_struct指针
|--p = copy_process(NULL, trace, NUMA_NO_NODE, args);
| //获取pid
|--pid = get_task_pid(p, PIDTYPE_PID);
| //获取虚拟的pid
| nr = pid_vnr(pid);
| if (clone_flags & CLONE_PARENT_SETTID)
| put_user(nr, args->parent_tid);
|--if (clone_flags & CLONE_VFORK)
| p->vfork_done = &vfork;
| init_completion(&vfork);
| get_task_struct(p);
| //将进程加入到就绪队列
|--wake_up_new_task(p);
|--if (clone_flags & CLONE_VFORK)
| //等待子进程调用exec()或exit()
| if (!wait_for_vfork_done(p, &vfork))
| ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
|--put_pid(pid);
\--return nr;