日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux0号进程,1号进程,2号进程

發(fā)布時間:2024/1/8 linux 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux0号进程,1号进程,2号进程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本節(jié)我們將從linux啟動的第一個進程說起,以及后面第一個進程是如何啟動1號進程,然后啟動2號進程。然后系統(tǒng)中所有的進程關系圖做個簡單的介紹

0號進程

0號進程,通常也被稱為idle進程,或者也稱為swapper進程。

0號進程是linux啟動的第一個進程,它的task_struct的comm字段為"swapper",所以也成為swpper進程。

#define INIT_TASK_COMM "swapper"

當系統(tǒng)中所有的進程起來后,0號進程也就蛻化為idle進程,當一個core上沒有任務可運行時就會去運行idle進程。一旦運行idle進程則此core就可以進入低功耗模式了,在ARM上就是WFI。

?

我們本節(jié)重點關注是0號進程是如何啟動的。在linux內核中為0號進程專門定義了一個靜態(tài)的task_struct的結構,稱為init_task。

/** Set up the first task table, touch at your own risk!. Base=0,* limit=0x1fffff (=2MB)*/ struct task_struct init_task = { #ifdef CONFIG_THREAD_INFO_IN_TASK.thread_info = INIT_THREAD_INFO(init_task),.stack_refcount = ATOMIC_INIT(1), #endif.state = 0,.stack = init_stack,.usage = ATOMIC_INIT(2),.flags = PF_KTHREAD,.prio = MAX_PRIO - 20,.static_prio = MAX_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,.tasks = LIST_HEAD_INIT(init_task.tasks),.ptraced = LIST_HEAD_INIT(init_task.ptraced),.ptrace_entry = LIST_HEAD_INIT(init_task.ptrace_entry),.real_parent = &init_task,.parent = &init_task,.children = LIST_HEAD_INIT(init_task.children),.sibling = LIST_HEAD_INIT(init_task.sibling),.group_leader = &init_task,RCU_POINTER_INITIALIZER(real_cred, &init_cred),RCU_POINTER_INITIALIZER(cred, &init_cred),.comm = INIT_TASK_COMM,.thread = INIT_THREAD,.fs = &init_fs,.files = &init_files,.signal = &init_signals,.sighand = &init_sighand,.blocked = {{0}},.alloc_lock = __SPIN_LOCK_UNLOCKED(init_task.alloc_lock),.journal_info = NULL,INIT_CPU_TIMERS(init_task).pi_lock = __RAW_SPIN_LOCK_UNLOCKED(init_task.pi_lock),.timer_slack_ns = 50000, /* 50 usec default slack */.thread_pid = &init_struct_pid,.thread_group = LIST_HEAD_INIT(init_task.thread_group),.thread_node = LIST_HEAD_INIT(init_signals.thread_head), }; EXPORT_SYMBOL(init_task);

這個結構體中的成員都是靜態(tài)定義了,為了簡單說明,對這個結構做了簡單的刪減。同時我們只關注這個結構中的以下幾個字段,別的先不關注。

  • .thread_info?? ?= INIT_THREAD_INFO(init_task),????? 這個結構在thread_info和內核棧的關系中有詳細的描述
  • .stack?? ??? ?= init_stack,???????????? init_stack就是內核棧的靜態(tài)的定義
  • .comm?? ??? ?= INIT_TASK_COMM,? 0號進程的名稱。

在這么thread_info和stack都涉及到了Init_stack, 所以先看下init_stack在哪里設置的。

?

最終發(fā)現init_task是在鏈接腳本中定義的。

#define INIT_TASK_DATA(align) \. = ALIGN(align); \__start_init_task = .; \init_thread_union = .; \init_stack = .; \KEEP(*(.data..init_task)) \KEEP(*(.data..init_thread_info)) \. = __start_init_task + THREAD_SIZE; \__end_init_task = .;

在鏈接腳本中定義了一個INIT_TASK_DATA的宏。

其中__start_init_task就是0號進程的內核棧的基地址,當然了init_thread_union=init_task=__start_init_task的。

而0號進程的內核棧的結束地址等于__start_init_task + THREAD_SIZE, THREAD_SIZE的大小ARM64一般是16K,或者32K。則__end_init_task就是0號進程的內核棧的結束地址。

?

?

Linux內核的啟動

熟悉linux內核的朋友都知道,linux內核的啟動 ,一般都是有bootloader來完成裝載,bootloader中會做一些硬件的初始化,然后會跳轉到linux內核的運行地址上去。

如果熟悉ARM架構的盆友也清楚,ARM64架構分為EL0, EL1, EL2, EL3。正常的啟動一般是從高特權模式向低特權模式啟動的。通常來說ARM64是先運行EL3,再EL2,然后從EL2就trap到EL1,也就是我們的Linux內核。

我們來看下Linux內核啟動的代碼。

代碼路徑:arch/arm64/kernel/head.S文件中 /** Kernel startup entry point.* ---------------------------** The requirements are:* MMU = off, D-cache = off, I-cache = on or off,* x0 = physical address to the FDT blob.** This code is mostly position independent so you call this at* __pa(PAGE_OFFSET + TEXT_OFFSET).** Note that the callee-saved registers are used for storing variables* that are useful before the MMU is enabled. The allocations are described* in the entry routines.*//** The following callee saved general purpose registers are used on the* primary lowlevel boot path:** Register Scope Purpose* x21 stext() .. start_kernel() FDT pointer passed at boot in x0* x23 stext() .. start_kernel() physical misalignment/KASLR offset* x28 __create_page_tables() callee preserved temp register* x19/x20 __primary_switch() callee preserved temp registers*/ ENTRY(stext)bl preserve_boot_argsbl el2_setup // Drop to EL1, w0=cpu_boot_modeadrp x23, __PHYS_OFFSETand x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0bl set_cpu_boot_mode_flagbl __create_page_tables/** The following calls CPU setup code, see arch/arm64/mm/proc.S for* details.* On return, the CPU will be ready for the MMU to be turned on and* the TCR will have been set.*/bl __cpu_setup // initialise processorb __primary_switch ENDPROC(stext)

上面就是內核在調用start_kernel之前做的主要工作了。

  • preserve_boot_args用來保留bootloader傳遞的參數,比如ARM上通常的dtb的地址
  • el2_setup:從注釋上來看是, 用來trap到EL1,說明我們在運行此指令前還在EL2
  • __create_page_tables: 用來創(chuàng)建頁表,linux才有的是頁面管理物理內存的,在使用虛擬地址之前需要設置好頁面,然后會打開MMU。目前還是運行在物理地址上的
  • __primary_switch: 主要任務是完成MMU的打開工作
__primary_switch:adrp x1, init_pg_dirbl __enable_mmuldr x8, =__primary_switchedadrp x0, __PHYS_OFFSETbr x8 ENDPROC(__primary_switch)
  • 主要是調用__enable_mmu來打開mmu,之后我們訪問的就是虛擬地址了
  • 調用__primary_switched來設置0號進程的運行內核棧,然后調用start_kernel函數
/** The following fragment of code is executed with the MMU enabled.** x0 = __PHYS_OFFSET*/ __primary_switched:adrp x4, init_thread_unionadd sp, x4, #THREAD_SIZEadr_l x5, init_taskmsr sp_el0, x5 // Save thread_infoadr_l x8, vectors // load VBAR_EL1 with virtualmsr vbar_el1, x8 // vector table addressisbstp xzr, x30, [sp, #-16]!mov x29, spstr_l x21, __fdt_pointer, x5 // Save FDT pointerldr_l x4, kimage_vaddr // Save the offset betweensub x4, x4, x0 // the kernel virtual andstr_l x4, kimage_voffset, x5 // physical mappings// Clear BSSadr_l x0, __bss_startmov x1, xzradr_l x2, __bss_stopsub x2, x2, x0bl __pi_memsetdsb ishst // Make zero page visible to PTWadd sp, sp, #16mov x29, #0mov x30, #0b start_kernel ENDPROC(__primary_switched)
  • init_thread_union就是我們在鏈接腳本中定義的,也就是0號進程的內核棧的棧底
  • add?? ?sp, x4, #THREAD_SIZE: 設置堆棧指針SP的值,就是內核棧的棧底+THREAD_SIZE的大小。現在SP指到了內核棧的頂端
  • 最終通過b start_kernel就跳轉到我們熟悉的linux內核入口處了。

至此0號進程就已經運行起來了。

?

1號進程

當一條b start_kernel指令運行后,內核就開始的內核的全面初始化操作

asmlinkage __visible void __init start_kernel(void) {char *command_line;char *after_dashes;set_task_stack_end_magic(&init_task);smp_setup_processor_id();debug_objects_early_init();cgroup_init_early();local_irq_disable();early_boot_irqs_disabled = true;/** Interrupts are still disabled. Do necessary setups, then* enable them.*/boot_cpu_init();page_address_init();pr_notice("%s", linux_banner);setup_arch(&command_line);/** Set up the the initial canary and entropy after arch* and after adding latent and command line entropy.*/add_latent_entropy();add_device_randomness(command_line, strlen(command_line));boot_init_stack_canary();mm_init_cpumask(&init_mm);setup_command_line(command_line);setup_nr_cpu_ids();setup_per_cpu_areas();smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */boot_cpu_hotplug_init();build_all_zonelists(NULL);page_alloc_init();。。。。。。。acpi_subsystem_init();arch_post_acpi_subsys_init();sfi_init_late();/* Do the rest non-__init'ed, we're now alive */arch_call_rest_init(); }void __init __weak arch_call_rest_init(void) {rest_init();

start_kernel函數就是內核各個重要子系統(tǒng)的初始化,比如mm, cpu, sched, irq等等。最后會調用一個rest_init剩余部分初始化

noinline void __ref rest_init(void) {struct task_struct *tsk;int pid;rcu_scheduler_starting();/** We need to spawn init first so that it obtains pid 1, however* the init task will end up wanting to create kthreads, which, if* we schedule it before we create kthreadd, will OOPS.*/pid = kernel_thread(kernel_init, NULL, CLONE_FS);/** Pin init on the boot CPU. Task migration is not properly working* until sched_init_smp() has been run. It will set the allowed* CPUs for init to the non isolated CPUs.*/rcu_read_lock();tsk = find_task_by_pid_ns(pid, &init_pid_ns);set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));rcu_read_unlock();numa_default_policy();pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);rcu_read_lock();kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);rcu_read_unlock();/** Enable might_sleep() and smp_processor_id() checks.* They cannot be enabled earlier because with CONFIG_PREEMPT=y* kernel_thread() would trigger might_sleep() splats. With* CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled* already, but it's stuck on the kthreadd_done completion.*/system_state = SYSTEM_SCHEDULING;complete(&kthreadd_done);}

在這個rest_init函數中我們只關系兩點:

  • pid = kernel_thread(kernel_init, NULL, CLONE_FS);
  • pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
/** Create a kernel thread.*/ 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); }

很明顯這是創(chuàng)建了兩個內核線程,而kernel_thread最終會調用do_fork根據參數的不同來創(chuàng)建一個進程或者內核線程。關系do_fork的實現我們在后面會做詳細的介紹。當內核線程創(chuàng)建成功后就會調用設置的回調函數。

當kernel_thread(kernel_init)成功返回后,就會調用kernel_init內核線程,其實這時候1號進程已經產生了。接下來看下kernel_init主要做什么事情

static int __ref kernel_init(void *unused) {int ret;kernel_init_freeable();/* need to finish all async __init code before freeing the memory */async_synchronize_full();ftrace_free_init_mem();free_initmem();mark_readonly();/** Kernel mappings are now finalized - update the userspace page-table* to finalize PTI.*/pti_finalize();system_state = SYSTEM_RUNNING;numa_default_policy();rcu_end_inkernel_boot();if (ramdisk_execute_command) {ret = run_init_process(ramdisk_execute_command);if (!ret)return 0;pr_err("Failed to execute %s (error %d)\n",ramdisk_execute_command, ret);}/** We try each of these until one succeeds.** The Bourne shell can be used instead of init if we are* trying to recover a really broken machine.*/if (execute_command) {ret = run_init_process(execute_command);if (!ret)return 0;panic("Requested init %s failed (error %d).",execute_command, ret);}if (!try_to_run_init_process("/sbin/init") ||!try_to_run_init_process("/etc/init") ||!try_to_run_init_process("/bin/init") ||!try_to_run_init_process("/bin/sh"))return 0;panic("No working init found. Try passing init= option to kernel. ""See Linux Documentation/admin-guide/init.rst for guidance."); }
  • kernel_init_freeable函數中就會做各種外設驅動的初始化
  • 最主要的工作就是通過execve執(zhí)行/init可以執(zhí)行文件。

我們通常將init稱為1號進程,其實在剛才kernel_init的時候1號線程已經創(chuàng)建成功,也可以理解kernel_init是1號進程的內核態(tài),而我們所熟知的init進程是用戶態(tài)的。

至此1號進程就完美的創(chuàng)建成功了,而且也成功執(zhí)行了init可執(zhí)行文件。

2號進程

2號進程,是由1號進程創(chuàng)建的。而且2號進程是所有內核線程父進程。

2號進程就是剛才rest_init中創(chuàng)建的另外一個內核線程。kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

當kernel_thread(kthreadd)返回時,2號進程已經創(chuàng)建成功了。而且會回調kthreadd函數

int kthreadd(void *unused) {struct task_struct *tsk = current;/* Setup a clean context for our children to inherit. */set_task_comm(tsk, "kthreadd");ignore_signals(tsk);set_cpus_allowed_ptr(tsk, cpu_all_mask);set_mems_allowed(node_states[N_MEMORY]);current->flags |= PF_NOFREEZE;cgroup_init_kthreadd();for (;;) {set_current_state(TASK_INTERRUPTIBLE);if (list_empty(&kthread_create_list))schedule();__set_current_state(TASK_RUNNING);spin_lock(&kthread_create_lock);while (!list_empty(&kthread_create_list)) {struct kthread_create_info *create;create = list_entry(kthread_create_list.next,struct kthread_create_info, list);list_del_init(&create->list);spin_unlock(&kthread_create_lock);create_kthread(create);spin_lock(&kthread_create_lock);}spin_unlock(&kthread_create_lock);}return 0; }

這段代碼大概的意思也很簡單明顯;

  • 設置當前進程的名字為"kthreadd",也就是task_struct的comm字段
  • 然后就是while循環(huán),設置當前的進程的狀態(tài)是TASK_INTERRUPTIBLE是可以中斷的
  • 判斷kthread_create_list鏈表是不是空,如果是空則就調度出去,讓出cpu
  • 如果不是空,則從鏈表中取出一個,然后調用kthread_create去創(chuàng)建一個內核線程。
  • 所以說所有的內核線程的父進程都是2號進程,也就是kthreadd。

?

?

總結:

  • linux啟動的第一個進程是0號進程,是靜態(tài)創(chuàng)建的
  • 在0號進程啟動后會接連創(chuàng)建兩個進程,分別是1號進程和2和進程。
  • 1號進程最終會去調用可init可執(zhí)行文件,init進程最終會去創(chuàng)建所有的應用進程。
  • 2號進程會在內核中負責創(chuàng)建所有的內核線程
  • 所以說0號進程是1號和2號進程的父進程;1號進程是所有用戶態(tài)進程的父進程;2號進程是所有內核線程的父進程。

我們通過ps命令就可以詳細的觀察到這一現象。

root@ubuntu:zhuxl$ ps -eF UID PID PPID C SZ RSS PSR STIME TTY TIME CMD root 1 0 0 56317 5936 2 Feb16 ? 00:00:04 /sbin/init root 2 0 0 0 0 1 Feb16 ? 00:00:00 [kthreadd]

上面很清晰的顯示:PID=1的進程是init,PID=2的進程是kthreadd。而他們倆的父進程PPID=0,也就是0號進程。

UID PID PPID C SZ RSS PSR STIME TTY TIME CMD root 4 2 0 0 0 0 Feb16 ? 00:00:00 [kworker/0:0H] root 6 2 0 0 0 0 Feb16 ? 00:00:00 [mm_percpu_wq] root 7 2 0 0 0 0 Feb16 ? 00:00:10 [ksoftirqd/0] root 8 2 0 0 0 1 Feb16 ? 00:02:11 [rcu_sched] root 9 2 0 0 0 0 Feb16 ? 00:00:00 [rcu_bh] root 10 2 0 0 0 0 Feb16 ? 00:00:00 [migration/0] root 11 2 0 0 0 0 Feb16 ? 00:00:00 [watchdog/0] root 12 2 0 0 0 0 Feb16 ? 00:00:00 [cpuhp/0] root 13 2 0 0 0 1 Feb16 ? 00:00:00 [cpuhp/1] root 14 2 0 0 0 1 Feb16 ? 00:00:00 [watchdog/1] root 15 2 0 0 0 1 Feb16 ? 00:00:00 [migration/1] root 16 2 0 0 0 1 Feb16 ? 00:00:11 [ksoftirqd/1] root 18 2 0 0 0 1 Feb16 ? 00:00:00 [kworker/1:0H] root 19 2 0 0 0 2 Feb16 ? 00:00:00 [cpuhp/2] root 20 2 0 0 0 2 Feb16 ? 00:00:00 [watchdog/2] root 21 2 0 0 0 2 Feb16 ? 00:00:00 [migration/2] root 22 2 0 0 0 2 Feb16 ? 00:00:11 [ksoftirqd/2] root 24 2 0 0 0 2 Feb16 ? 00:00:00 [kworker/2:0H]

再來看下,所有內核線性的PPI=2, 也就是所有內核線性的父進程都是kthreadd進程。

UID PID PPID C SZ RSS PSR STIME TTY TIME CMD root 362 1 0 21574 6136 2 Feb16 ? 00:00:03 /lib/systemd/systemd-journald root 375 1 0 11906 2760 3 Feb16 ? 00:00:01 /lib/systemd/systemd-udevd systemd+ 417 1 0 17807 2116 3 Feb16 ? 00:00:02 /lib/systemd/systemd-resolved systemd+ 420 1 0 35997 788 3 Feb16 ? 00:00:00 /lib/systemd/systemd-timesyncd root 487 1 0 43072 6060 0 Feb16 ? 00:00:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers root 489 1 0 8268 2036 2 Feb16 ? 00:00:00 /usr/sbin/cron -f root 490 1 0 1138 548 0 Feb16 ? 00:00:01 /usr/sbin/acpid root 491 1 0 106816 3284 1 Feb16 ? 00:00:00 /usr/sbin/ModemManager root 506 1 0 27628 2132 2 Feb16 ? 00:00:01 /usr/sbin/irqbalance --foreground

所有用戶態(tài)的進程的父進程PPID=1,也就是1號進程都是他們的父進程。

?

至此有關0號進程,1號進程,2號進程的內容分析完畢。

總結

以上是生活随笔為你收集整理的Linux0号进程,1号进程,2号进程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。