进程调度二 进程创建do_fork

一、前言

  kernel在启动初期并没有“进程”这个概念,因为不涉及多任务并发、调度,kernel 起来后会在start_kernel

中创建kthread和init进程,在0号进程的基础上创建init进程(PID为1),0 进程会被设置成idle进程,加入到运行

队列中。当CPU上没有可调度进程时,调度器才会选择0号进程运行:

int start_kernel(){
    ......
    kernel_thread(kernel_init, NULL, CLONE_FS);
    kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    ......
}

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
	return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
		(unsigned long)arg, NULL, NULL, 0);
}

  从上面的代码中可以看出,在start_kernel阶段会创建kthread和Init的进程,init进程是所有用户进程的父进程,

kthread进程是内核进程的父进程,下面会重点_do_fork创建进程的流程。

二、do_fork流程:

long _do_fork(unsigned long clone_flags,//创建进程标志位
	      unsigned long stack_start,    //用户态栈的起始地址
	      unsigned long stack_size,     //用户态栈的大小
	      int __user *parent_tidptr,
	      int __user *child_tidptr,
	      unsigned long tls)

1、写时复制技术(copy on write,COW):

  进程的使用了写时拷贝技术,为了减少创建线程时的工作量,采用了写时复制技术,子进程只复制父进程的页表,

不会复制页面内容,当子进程需要写入新内容时才触发写时复制机制,为子进程创建一个副本。写时拷贝是一种可以

推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用

在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。

2、clone_flags标记位:

#define CSIGNAL		0x000000ff	/* signal mask to be sent at exit */
#define CLONE_VM	0x00000100	/* 父子进程共享内存空间 */
#define CLONE_FS	0x00000200	/* 父子进程共享文件系统 */
#define CLONE_FILES	0x00000400	/* 父子进程共享文件描叙符 */
#define CLONE_SIGHAND	0x00000800	/* 父子进程共享相同的信号处理 */
#define CLONE_PTRACE	0x00002000	/* 父进程被trace,子进程同样被trace */
#define CLONE_VFORK	0x00004000	/* 父进程被挂起,直到子进程释放了虚拟内存资源(exit/execve) */
#define CLONE_THREAD	0x00010000	/* 父子进程共享相同的线程群 */
#define CLONE_NEWNS	0x00020000	/* New mount namespace group */
#define CLONE_SYSVSEM	0x00040000	/* share system V SEM_UNDO semantics */
#define CLONE_SETTLS	0x00080000	/* create a new TLS for the child */
#define CLONE_PARENT_SETTID	0x00100000	/* set the TID in the parent */

3、其中do_fork所做的工作可以用下图来初步的描叙:

(1)do_fork主要调用copy_process()函数来创建一个新的进程,copy_process函数非常长,可以分解为如下:

a、标志位clone_flags检查:

  • CLONE_NEWNS表示父子进程不共享mount namespace,CLONE_FS父子进程共享文件系统,两则不能同时使用;
  • CLONE_THREAD表示进程在同一个线程组里,CLONE_SIGHAND表示父子进程共享相同的信号处理表;
  • CLONE_PARENT表示新建的进程是兄弟进程,而不是父子进程;
  • CLONE_NEWPID表示一个新的PID namespace;

b、dump_task_struct( )分配task_struct:

  • struct task_struct是描叙进程的核心数据结构,用于描叙进程的状态信息和控制信息;
  • struct thread_info用于村粗描叙符频繁访问和硬件快速访问的字段;
  • tsk = alloc_task_struct_node(node)通过slub先分配一个task_struct的结构体的内存;
  • stack = alloc_thread_stack_node(tsk, node) 分配内核栈所用的内存(32bit为8kB,64bit为16KB),此块内存每创建一个用户进程都会申请对应空间的大小:
    static unsigned long *alloc_thread_stack_node(struct task_struct *tsk,
    						  int node)
    {
    //#define THREAD_SIZE_ORDER	1  ===> 32bit为1,申请2 * 4kB = 8kB的大小
    //#define THREAD_SIZE_ORDER	2  ===> 64bit为2,申请4 * 4kB = 16KB的大小
    	struct page *page = alloc_kmem_pages_node(node, THREADINFO_GFP,
    						  THREAD_SIZE_ORDER);
    	return page ? page_address(page) : NULL;
    }
  • arch_dup_task_struct(tsk, orig)拷贝父进程的task_struct数据结构的内容到子进程中;

  • setup_thread_stack(tsk, orig)拷贝父进程的thread_info数据结构的内容到子进程中;

c、sched_fork(clone_flags, p)初始化进程调度相关的数据结构,如调度实体;

d、复制父进程的信息:

  • copy_files(clone_flags, p)复制父进程的打开的文件等信息;
  • copy_fs(clone_flags, p)复制父进程fs_struct结构的信息;
  • copy_sighand(clone_flags, p)复制父进程的信号系统;
  • copy_io(clone_flags, p)复制父进程的IO相关信息;
  • copy_mm(clone_flags, p)复制父进程的内存空间,这里只会复制pte页表项,并没有复制VMA对应的页面的内容(写时复制);

(2)在copy_proces()函数中成功创建一个进程后,会调用wake_up_new_task(p)将对应的进程加入

调度器中等待调度执行。

作者:frank_zyp
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文无所谓版权,欢迎转载。

猜你喜欢

转载自blog.csdn.net/frank_zyp/article/details/85327951
今日推荐