进程pid是什么意思(「Linux内核」进程0是什么?Init进程(PID=1))

我们知道,对于内核提供的进程管理子系统,以后肯定是要运行各种进程的。对于我们这些做Linux内核开发的人来说,熟悉Linux下的三个特殊过程,主要内容如下:

  • 空闲进程(PID = 0)。本章中的进程0是什么?
  • 初始化进程(PID = 1),本章的进程1是什么?
  • Kthread(PID = 2),本章的进程2是什么?
  • 进程初始化(0号进程)

    内核的启动从入口函数start_kernel()开始;在init/main.c文件中,start_kernel相当于内核的main函数;

    里面有各种初始化函数,用来初始化各个子系统。对于操作系统,启动时会创建第一个进程,也就是唯一没有通过fork生成的进程。首先,内核需要分配init_task进程的task_struct数据结构。

    1.1 进程描述符分配

    第0个流程描述符变量是Init_task,在init/init_task.c文件中静态初始化,其代码实现如下:

    结构任务_结构初始化任务=初始化任务(初始化任务); EXPORT _ SYMBOL(init _ task);宏INIT_TASK在文件include/linux/init_task.h中定义:

    # define INIT _ TASK(tsk) { INIT _ TASK _ TI(tsk) 。state = 0, 。stack = init_stack, 。用法= ATOMIC_INIT(2), 。flags = PF_KTHREAD, 。prio = MAX_PRIO-20, 。静态优先=最大PRIO-20, 。normal_prio = MAX_PRIO-20, 。policy = SCHED_NORMAL, 。cpus _ allowed = CPU _ MASK _ ALL, 。nr _ cpus _ allowed = NR _ CPUS, 。mm = NULL, 。active_mm = &init_mm, 。restart_block = { 。fn = do_no_restart_syscall, }, 。se = { 。group _ node = LIST _ HEAD _ INIT(tsk . se . group _ node), }, 。rt = { 。run _ LIST = LIST _ HEAD _ INIT(tsk . rt . run _ LIST), 。time_slice = RR_TIMESLICE, }, 。tasks = LIST_HEAD_INIT(tsk.tasks), ... 。comm = INIT_TASK_COMM, 。thread = INIT_THREAD, 。fs = &init_fs, 。文件= &init_files, 。signal = &init_signals, 。Sighand = & init _ Sighand, 。nsproxy = & init _ nsproxy, }在comm字段中,进程0被称为swapper。另外,系统中所有进程的task_struct数据结构都是通过一个list_head类型的双向链表链接在一起的,所以每个进程的任务这个进程链表的头就是init_task进程,也称为process 0。

    更多Linux内核视频教程文档和资料可以在后台私信免费获取[内核]。



    1.2 进程堆栈

    init_task进程使用init_thread_union数据结构描述的内存区域作为进程的stack 空空间,并与自己的thread_info参数共享这个内存空空间。

    # define init _ task(TSK) { ... 。stack = init _ stack, ... }而init_thread_info是/arch/中定义的架构相关定义。

    # define init _ thread _ info(init_thread_union . thread _ info) # define init _ stack(init _ thread _ union . stack)和init _ thread _ union是在init/init/init_task.c中定义的

    union THREAD _ union INIT _ THREAD _ union _ _ INIT _ TASK _ data = { # ifndef CONFIG _ THREAD _ INFO _ IN _ TASK INIT _ THREAD _ INFO(INIT _ TASK) # endif }; # define INIT _ THREAD _ INFO(tsk) { 。task = &tsk, 。flags = 0, 。PREEMPT _ COUNT = INIT _ PREEMPT _ COUNT, 。addr _ limit = kernel _ ds, }1.3 process空由于init_task是运行在kernel空之间的内核线程,其虚拟地址段mm为空。

    #定义INIT_TASK(tsk) { 。mm = NULL, 。active_mm = &init_mm, } struct mm _ struct init _ mm = { 。mm_rb = RB_ROOT, 。pgd = swapper_pg_dir, 。mm_users = ATOMIC_INIT(2), 。mm_count = ATOMIC_INIT(1), 。mmap _ SEM = _ _ RWSEM _ INITIALIZER(init _ mm . mmap _ SEM), 。page _ table _ LOCK = _ _ SPIN _ LOCK _ UNLOCKED(init _ mm . page _ table _ LOCK), 。mm LIST = LIST _ HEAD _ INIT(INIT _ mm . mm LIST), 。user_ns = &init_user_ns, INIT _ MM _ CONTEXT(INIT _ MM) };对于普通的进程,这两个指针变量有相同的值。然而,内核线程没有任何内存描述符,所以它们的mm成员总是空的。当init_task内核线程运行时,其active_mm成员被初始化为init _ mm的值。

    2 其他初始化

    内核启动阶段的最后一个函数reset_init()函数,内部再次调用几个函数,最终会创建1号进程和2号进程。

    static no inline void _ _ ref rest _ init(void) { int PID; rcu _ scheduler _ starting(); /* *我们需要首先生成init,以便它获得pid 1,但是 * init任务将最终想要创建kthreads,如果 *我们在创建kthread之前安排它,将会出错。 */ kernel _ thread(kernel _ init,NULL,CLONE _ FS);-(1) numa _ default _ policy(); PID = kernel _ thread(kthread,NULL,CLONE _ FS | CLONE _ FILES);-(2) rcu _ read _ lock(); kthreadd _ task = find _ task _ by _ PID _ ns(PID,& init _ PID _ ns); rcu _ read _ unlock(); 完成(& kthreadd _ done); /* *引导空闲线程必须至少执行一次schedule() *才能让事情运转起来: */ init _ idle _ boot up _ task(当前);-(3) schedule _ preempt _ disabled();- (4) /*在preempt禁用的情况下调入CPU _ idle */ CPU _ startup _ entry(CPU HP _ ONLINE);- (5) }调用kernel_thread()创建1号内核线程,然后转到user 空 room,成为init进程。

  • 调用kernel_thread()创建kthread内核线程
  • Init_idle_bootup_task():目前0号进程init_task最终会退化为空闲进程,所以这里调用init_idle_bootup_task()函数,使init_task进程属于空闲调度类。即,选择空闲的调度相关函数。
  • 调用schedule()函数切换当前进程。在调用这个函数之前,Linux系统中只有两个进程,即0号进程init_task和1号进程kernel_init,其中kernel_init进程刚刚被创建。调用这个函数后,第一个进程kernel_init将运行。
  • 调用cpu_idle(),0号线程进入idle函数的循环,会定期检查。
  • 2.1 初始化1号进程

    init进程是内核在启动过程中产生的一个进程,它的PID是1。它生成所有用户进程并监控它们的运行,并保持执行状态直到系统结束。Kernel _ thread (kernel _ init,null,clone _ fs)创建第二个进程,也就是1号进程。

    static int _ _ ref kernel _ init(void * unused) { int ret; kernel _ init _ freeable(); /*需要在释放内存之前完成所有async __init代码*/ async _ synchronize _ full();-(1) free _ init mem(); mark _ readonly(); SYSTEM _ state = SYSTEM _ RUNNING; numa _ default _ policy(); rcu _ end _ in kernel _ boot(); if(ramdisk _ execute _ command){-(2) ret = run _ init _ process(ramdisk _ execute _ command); 如果(!ret) 返回0; pr _ err(& # 34;未能执行%s(错误% d)n & # 34;, ramdisk_execute_command,ret); } /* *我们尝试每一种方法,直到有一种成功。 * *如果我们 *试图恢复一台真正损坏的机器,可以使用Bourne shell来代替init。 */ if(execute _ command){-(3) ret = run _ init _ process(execute _ command); 如果(!ret) 返回0; 恐慌(& # 34;请求的初始化%s失败(错误%d)。", execute_command,ret); } if(!try _ to _ run _ init _ process(& # 34;/sbin/init & # 34;)|| - (4) !try _ to _ run _ init _ process(& # 34;/etc/init & # 34;)|| !try _ to _ run _ init _ process(& # 34;/bin/init & # 34;)|| !try _ to _ run _ init _ process(& # 34;/bin/sh & # 34;)) 返回0; 慌(& # 34;找不到工作初始化。尝试将init= option传递给内核。" & # 34;请参阅Linux Documentation/init.txt以获取指导。"); }在async _ synchronize _ full中结束所有异步操作,准备释放内存,释放。init . data free _ init mem中所有初始化函数和函数使用的内存区域。

    pid是什么意思

    内核模式下的1号kernel_init进程将被转换为user 空下的1号init进程。进程init将根据/etc/inittab中提供的信息完成应用程序的初始化调用。然后init进程会执行/bin/sh生成一个shell接口供用户与Linux系统交互,调用run_init_process()创建用户态一号进程。

    2.2 初始化2号进程

    内核线程守护进程是一个执行内核线程的守护进程。内核启动时,会生成所有注册到kthread_create_list的内核线程。下面是kthreadd函数:

    int kthread(void * unused) { struct task _ struct * tsk = current;//获得当前的宠爱 /*为我们的孩子建立一个干净的上下文来继承。*/ Set _ task _ comm (TSK,& # 34;kthreadd & # 34);//配置2号进程名称KTHREAD ignore _ signals(TSK);//设置任务信号处理忽略所有信号 Set _ CPU _ allowed _ ptr(TSK,CPU _ all _ mask);//允许kthreadd在任意CPU上运行,设置affinity set _ MEMS _ allowed(node _ States[n _ memory]); 当前-& gt;flags | = PF _ NOFREEZE cgroup _ init _ kthread(); for(;;){ //首先将线程状态设置为TASK_INTERRUPTIBLE。如果没有线程要创建,放弃CPU完成调度。该进程被阻塞 set _ current _ state(task _ interruptible); if(list _ empty(& kthread _ create _ list))//没有要创建的内核线程 schedule();//执行一个放弃CPU的调度 _ _ set _ current _ state(task _ running);//此处运行表示唤醒了KTHREAD线程 //将进程的运行状态设置为task _ Running spin _ LOCK(& KTHREAD _ CREATE _ LOCK); while(!list _ empty(& kthread _ create _ list)){-(1) struct kthread _ create _ info * create; create = list _ entry(kthread _ create _ list . next, struct kthread_create_info,list); list _ del _ init(& create-& gt;列表); spin _ unlock(& kthread _ create _ lock); create _ kthread(create); spin _ lock(& kthread _ create _ lock); } spin _ unlock(& kthread _ create _ lock); } 返回0; }代码1是kthread函数的核心部分。如果内核线程列表kthread_create_list不是空,调用list_entry函数使kthread_create_info结构的创建成员执行kthread_create_list的第一个成员。生成的内核线程的信息,定义如下:

    kthread _ create _ list struct kthread _ create _ info的成员 { /*信息从kthread传递到kthread()。*/ int(* thread fn)(void * data);//要执行的函数 void * data;//传递给函数 int节点的数据; /*结果从kthread传递回kthread_create()。*/ struct task _ struct *结果;//生成内核线程后的任务 struct completion * done;//通知已经结束 struct list _ head list;//连接到kthread_create_list成员 };所以代码大致做了以下事情"

    将当前进程的名称设置为kthreadd,即task_struct的comm字段;那么对于loop,将当前流程状态设置为TASK_INTERRUPTIBLE就可以中断;判断kthread_create_list链表是否空,如果是空,则调度其放弃CPU;如果不是空,从链表中取出,调用create_kthread创建内核线程;因此,所有内核线程的父进程是2号进程,即kthread。

    3. idle进程

    Linux内核会在系统启动后的空闲进程中处理CPU相关事宜。在多核系统中,CPU启动过程是先启动主机CPU,和传统单核系统类似。函数调用关系如下:

    stext-& gt;start _ kernel–& gt;rest _ init-& gt;Cpu_startup_entry启动其他Cpu,有很多种方式,比如CPU热插拔等。启动过程:

    secondary_startup –> __secondary_switched –> secondary_start_kernel –> cpu_startup_entry


    secondary _ startup-& gt;_ _ secondary _ switched-& gt;secondary _ start _ kernel–& gt;cpu_startup_entry

    在这个函数中,最终程序会陷入无限循环cpu_idle_loop。至此,空闲流程的创建完成,下面是空闲流程的代码实现。

    静态void CPU _ idle _ loop(void) { int CPU = SMP _ processor _ id(); while(1){ _ _ current _ set _ polling(); quiet _ vmstat(); tick _ nohz _ idle _ enter();//关闭周期滴答,CONFIG_NO_HZ_IDLE必须打开 while(!Need_resched()) {//如果系统目前不需要调度,执行后续动作 check _ pgt _ cache(); 人民币(); if(CPU _ is _ offline(CPU)){ CPU HP _ report _ idle _ dead(); arch _ CPU _ idle _ dead(); } local _ IRQ _ disable();//关闭irq中断 arch _ CPU _ idle _ enter();//arch相关的cpuidle enter //主要执行通知回调 if(CPU _ idle _ force _ poll | | | tick _ check _ broadcast _ expired()) CPU _ idle _ poll();//idle pill else c guidle _ idle _ call();//进入cpu空闲模式省电 arch _ CPU _ idle _ exit();//idle退出,主要是注册idle的notify回调 } //如果系统当前需要调度,退出idle进程 preempt _ set _ need _ rescuved(); tick _ nohz _ idle _ exit();//打开周期滴答 _ _ current _ clr _ polling(); SMP _ MB _ _ after _ atomic(); sched _ TT Wu _ pending(); schedule _ preempt _ disabled();//放弃cpud意味着调度器调度其他优先级更高的进程 } }系统的周期滴答可以动态关闭和开启。这个功能可以通过内核配置项CONFIG_NO_HZ开启,IDLE就是利用这个技术让系统尽可能长时间的处于空空闲状态,从而尽可能的节省功耗。这个内容比较多,后面单独研究。

    总结

    Linux启动的第一个进程是0号进程,它是静态创建的。那么0号流程启动后,会创建两个流程,即1号流程和2号流程

    0号进程是系统创建的第一个进程,也是唯一不是由fork或kernel_thread生成的进程。加载系统后,会演变成一个空闲进程。

    1号(init)进程是由idle通过kernel_thread创建的。kernel 空之间初始化后,最终会调用init可执行文件,init进程最终会创建所有的应用进程,是其他用户进程的祖先。

    2号(kthread)进程由空闲进程通过kernel_thread创建,始终运行在kernel空之间,负责所有内核线程的调度和管理。



    从上图可以看出,PID=1的进程是init,PID=2的进程是kthread,它们的父进程PPID=0,也就是0号进程,回头看,所有内核线程的PPID=2是2,页面表示内核线程的父进程是kthread进程。



    所有用户模式进程的父进程PPID=1,也就是说,1号进程是它们的父进程。用户模式没有括号,内核模式有括号。关系图如下图所示:



    您可以还会对下面的文章感兴趣

    使用微信扫描二维码后

    点击右上角发送给好友