分享更有价值
被信任是一种快乐

linux中0号进程的含义是什么

文章页正文上

这篇文章主要介绍“linux中0号进程的含义是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“linux中0号进程的含义是什么”文章能帮助大家解决问题。 在linux中,0号进程是指idle进程,是linux启动的第一个进程;它的task_struct的comm字段为“swapper”,所以也称为swpper进程。0号进程是唯一一个没有通过fork或者kernel_thread产生的进程,因为init_task是静态变量(初始化了的全局变量),其他进程的PCB都是fork或者kernel_thread动态申请内存创建的。0号进程,通常也被称为idle进程,或者也称为swapper进程。每个进程都有一个进程控制块PCB(Process Control Block),PCB的数据结构类型是struct task_struct。idle进程对应的PCB是 struct task_struct init_task。idle进程是唯一一个没有通过fork或者kernel_thread产生的进程,因为 init_task 是静态变量(初始化了的全局变量),其他进程的PCB都是fork或者kernel_thread动态申请内存创建的。每个进程都有对应的一个函数,idle进程的函数是 start_kernel(),因为进入该函数前,栈指针SP已经指向 init_task 的栈顶了,处于什么进程,看SP指向哪个进程的栈。0号进程是linux启动的第一个进程,它的task_struct的comm字段为”swapper”,所以也称为swpper进程。当系统中所有的进程起来后,0号进程也就蜕化为idle进程,当一个core上没有任务可运行时就会去运行idle进程。一旦运行idle进程则此core就可以进入低功耗模式了,在ARM上就是WFI。我们本节重点关注是0号进程是如何启动的。在linux内核中为0号进程专门定义了一个静态的task_struct的结构,称为init_task。

/*
*Setupthefirsttasktable,touchatyourownrisk!.Base=0,
*limit=0x1fffff(=2MB)
*/
structtask_structinit_task
={
#ifdefCONFIG_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,/*50usecdefaultslack*/
.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);

这个结构体中的成员都是静态定义了,为了简单说明,对这个结构做了简单的删减。同时我们只关注这个结构中的以下几个字段,别的先不关注。.thread_info = INIT_THREAD_INFO(init_task), 这个结构在thread_info和内核栈的关系中有详细的描述.stack = init_stack, init_stack就是内核栈的静态的定义.comm = INIT_TASK_COMM, 0号进程的名称。在这么thread_info和stack都涉及到了Init_stack, 所以先看下init_stack在哪里设置的。最终发现init_task是在链接脚本中定义的。

#defineINIT_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号进程的内核栈的结束地址。idle进程由系统自动创建, 运行在内核态,idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换。熟悉linux内核的朋友都知道,linux内核的启动 ,一般都是有bootloader来完成装载,bootloader中会做一些硬件的初始化,然后会跳转到linux内核的运行地址上去。如果熟悉ARM架构的盆友也清楚,ARM64架构分为EL0, EL1, EL2, EL3。正常的启动一般是从高特权模式向低特权模式启动的。通常来说ARM64是先运行EL3,再EL2,然后从EL2就trap到EL1,也就是我们的Linux内核。我们来看下Linux内核启动的代码。代码路径:arch/arm64/kernel/head.S文件中

/*
*Kernelstartupentrypoint.
*---------------------------
*
*Therequirementsare:
*MMU=off,D-cache=off,I-cache=onoroff,
*x0=physicaladdresstotheFDTblob.
*
*Thiscodeismostlypositionindependentsoyoucallthisat
*__pa(PAGE_OFFSET+TEXT_OFFSET).
*
*Notethatthecallee-savedregistersareusedforstoringvariables
*thatareusefulbeforetheMMUisenabled.Theallocationsaredescribed
*intheentryroutines.
*/
/*
*Thefollowingcalleesavedgeneralpurposeregistersareusedonthe
*primarylowlevelbootpath:
*
*RegisterScopePurpose
*x21stext()..start_kernel()FDTpointerpassedatbootinx0
*x23stext()..start_kernel()physicalmisalignment/KASLRoffset
*x28__create_page_tables()calleepreservedtempregister
*x19/x20__primary_switch()calleepreservedtempregisters
*/
ENTRY(stext)
blpreserve_boot_args
blel2_setup//DroptoEL1,w0=cpu_boot_mode
adrpx23,__PHYS_OFFSET
andx23,x23,MIN_KIMG_ALIGN-1//KASLRoffset,defaultsto0
blset_cpu_boot_mode_flag
bl__create_page_tables
/*
*ThefollowingcallsCPUsetupcode,seearch/arm64/mm/proc.Sfor
*details.
*Onreturn,theCPUwillbereadyfortheMMUtobeturnedonand
*theTCRwillhavebeenset.
*/
bl__cpu_setup//initialiseprocessor
b__primary_switch
ENDPROC(stext)

上面就是内核在调用start_kernel之前做的主要工作了。preserve_boot_args用来保留bootloader传递的参数,比如ARM上通常的dtb的地址el2_setup:从注释上来看是, 用来trap到EL1,说明我们在运行此指令前还在EL2__create_page_tables: 用来创建页表,linux才有的是页面管理物理内存的,在使用虚拟地址之前需要设置好页面,然后会打开MMU。目前还是运行在物理地址上的__primary_switch: 主要任务是完成MMU的打开工作主要是调用__enable_mmu来打开mmu,之后我们访问的就是虚拟地址了调用__primary_switched来设置0号进程的运行内核栈,然后调用start_kernel函数

/*
*ThefollowingfragmentofcodeisexecutedwiththeMMUenabled.
*
*x0=__PHYS_OFFSET
*/
__primary_switched:
adrpx4,init_thread_union
addsp,x4,#THREAD_SIZE
adr_lx5,init_task
msrsp_el0,x5//Savethread_info

adr_lx8,vectors//loadVBAR_EL1withvirtual
msrvbar_el1,x8//vectortableaddress
isb

stpxzr,x30,[sp,#-16]!
movx29,sp

str_lx21,__fdt_pointer,x5//SaveFDTpointer

ldr_lx4,kimage_vaddr//Savetheoffsetbetween
subx4,x4,x0//thekernelvirtualand
str_lx4,kimage_voffset,x5//physicalmappings

//ClearBSS
adr_lx0,__bss_start
movx1,xzr
adr_lx2,__bss_stop
subx2,x2,x0
bl__pi_memset
dsbishst//MakezeropagevisibletoPTW

addsp,sp,#16
movx29,#0
movx30,#0
bstart_kernel
ENDPROC(__primary_switched)

init_thread_union就是我们在链接脚本中定义的,也就是0号进程的内核栈的栈底add sp, x4, #THREAD_SIZE: 设置堆栈指针SP的值,就是内核栈的栈底+THREAD_SIZE的大小。现在SP指到了内核栈的顶端最终通过b start_kernel就跳转到我们熟悉的linux内核入口处了。  至此0号进程就已经运行起来了。3.1 1号进程的创建  当一条b start_kernel指令运行后,内核就开始的内核的全面初始化操作。

asmlinkage__visiblevoid__initstart_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;
/*
*Interruptsarestilldisabled.Donecessarysetups,then
*enablethem.
*/
boot_cpu_init();
page_address_init();
pr_notice("%s",linux_banner);
setup_arch(&command_line);
/*
*Setupthetheinitialcanaryandentropyafterarch
*andafteraddinglatentandcommandlineentropy.
*/
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-specificboot-cpuhooks*/
boot_cpu_hotplug_init();
build_all_zonelists(NULL);
page_alloc_init();
。。。。。。。
acpi_subsystem_init();
arch_post_acpi_subsys_init();
sfi_init_late();
/*Dotherestnon-__init'ed,we'renowalive*/
arch_call_rest_init();
}
void__init__weakarch_call_rest_init(void)
{
rest_init();
}

start_kernel函数就是内核各个重要子系统的初始化,比如mm, cpu, sched, irq等等。最后会调用一个rest_init剩余部分初始化,start_kernel在其最后一个函数rest_init的调用中,会通过kernel_thread来生成一个内核进程,后者则会在新进程环境下调 用kernel_init函数,kernel_init一个让人感兴趣的地方在于它会调用run_init_process来执行根文件系统下的 /sbin/init等程序。

noinlinevoid__refrest_init(void)
{
structtask_struct*tsk;
intpid;
rcu_scheduler_starting();
/*
*Weneedtospawninitfirstsothatitobtainspid1,however
*theinittaskwillendupwantingtocreatekthreads,which,if
*wescheduleitbeforewecreatekthreadd,willOOPS.
*/
pid=kernel_thread(kernel_init,NULL,CLONE_FS);
/*
*PininitonthebootCPU.Taskmigrationisnotproperlyworking
*untilsched_init_smp()hasbeenrun.Itwillsettheallowed
*CPUsforinittothenonisolatedCPUs.
*/
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();
/*
*Enablemight_sleep()andsmp_processor_id()checks.
*TheycannotbeenabledearlierbecausewithCONFIG_PREEMPT=y
*kernel_thread()wouldtriggermight_sleep()splats.With
*CONFIG_PREEMPT_VOLUNTARY=ytheinittaskmighthavescheduled
*already,butit'sstuckonthekthreadd_donecompletion.
*/
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);  很明显这是创建了两个内核线程,而kernel_thread最终会调用do_fork根据参数的不同来创建一个进程或者内核线程。关系do_fork的实现我们在后面会做详细的介绍。当内核线程创建成功后就会调用设置的回调函数。  免费云主机、域名当kernel_thread(kernel_init)成功返回后,就会调用kernel_init内核线程,其实这时候1号进程已经产生了。1号进程的执行函数就是kernel_init, 这个函数被定义init/main.c中,接下来看下kernel_init主要做什么事情。

staticint__refkernel_init(void*unused)
{
intret;
kernel_init_freeable();
/*needtofinishallasync__initcodebeforefreeingthememory*/
async_synchronize_full();
ftrace_free_init_mem();
free_initmem();
mark_readonly();
/*
*Kernelmappingsarenowfinalized-updatetheuserspacepage-table
*tofinalizePTI.
*/
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)
return0;
pr_err("Failedtoexecute%s(error%d)n",
ramdisk_execute_command,ret);
}
/*
*Wetryeachoftheseuntilonesucceeds.
*
*TheBourneshellcanbeusedinsteadofinitifweare
*tryingtorecoverareallybrokenmachine.
*/
if(execute_command){
ret=run_init_process(execute_command);
if(!ret)
return0;
panic("Requestedinit%sfailed(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"))
return0;
panic("Noworkinginitfound.Trypassinginit=optiontokernel."
"SeeLinuxDocumentation/admin-guide/init.rstforguidance.");
}

kernel_init_freeable函数中就会做各种外设驱动的初始化。最主要的工作就是通过execve执行/init可以执行文件。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号…的若干终端注册进程getty。每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。我们通常将init称为1号进程,其实在刚才kernel_init的时候1号线程已经创建成功,也可以理解kernel_init是1号进程的内核态,而我们所熟知的init进程是用户态的,调用execve函数之前属于内核态,调用之后就属于用户态了,执行的代码段与0号进程不在一样。1号内核线程负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。至此1号进程就完美的创建成功了,而且也成功执行了init可执行文件。  3.2 init进程  随后,1号进程调用do_execve运行可执行程序init,并演变成用户态1号进程,即init进程。  init进程是linux内核启动的第一个用户级进程。init有许多很重要的任务,比如像启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。  它按照配置

相关推荐: telnet协议有什么作用

这篇文章主要介绍“telnet协议有什么作用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“telnet协议有什么作用”文章能帮助大家解决问题。答:telnet是Internet远程登录服务的标准协议。 它是我们所熟知的…

文章页内容下
赞(0) 打赏
版权声明:本站采用知识共享、学习交流,不允许用于商业用途;文章由发布者自行承担一切责任,与本站无关。
文章页正文下
文章页评论上

云服务器、web空间可免费试用

宝塔面板主机、支持php,mysql等,SSL部署;安全高速企业专供99.999%稳定,另有高防主机、不限制内容等类型,具体可咨询QQ:360163164,Tel同微信:18905205712

主机选购导航云服务器试用

登录

找回密码

注册