内核线程和用户线程

---- 在Ubuntu系统下,使用 ps -axjf(ps -eLf) 命令可以查看详细的内核线程和用户线程状态。

ps:report a snapshot of the current processes, displays information about a selection of the active processes.

top: if you want a repetitive update of the selection and the displayed information.

 # ps
 USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
 root      1     0     396    256   c45ebcd0 0000875c S /init
 root      2     0     0      0     c4570cec 00000000 S kthreadd
 root      3     2     0      0     c45618e8 00000000 S ksoftirqd/0
 root      4     2     0      0     c4599578 00000000 S watchdog/0
 root      5     2     0      0     c456d8bc 00000000 S events/0
 root      6     2     0      0     c456d8bc 00000000 S cpuset
 root      7     2     0      0     c456d8bc 00000000 S khelper

上面的ps命令列出了1号进程/init,2号进程kthreadd,其他的线程,基本都是1号和2号进程的子进程(子线程)。

PID指示该进程的进程号,PPID指示该进程的父进程号

可以看出,1号和2号进程的父进程就是0号线程,PPID=0,是Kernel在start_kernel中创建的第一个线程,称为Idle线程

当没有其他线程在运行时,schedule将调度它进入Idle省电状态

由于0号进程是一个特殊的进程,所以ps命令并没有把它列出来。

ps命令参数解析:

-A:Select all processes. Identical to -e

-a:Select all processes except both session leaders (see getsid(2)) and processes not associated with a terminal.

-u:代表user,在需要查看特定用户进程的情况下使用,如ps -u dong

-f :do full-format listing(全格式列出)

-L:NLMP(number of threads),LMP(thread id)columns will be added

如果想根据CPU或内存用量来筛选,用-aux参数

---- 线程与进程

在学习Linux的知识时,书上经常会有线程和进程的提法,在Linux内核中,其实是不区分进程还是线程的。

Linux kernel会使用一个统一的task_struct (进程描述符)来描述任务相关的所有信息。

然而我们该怎么样来理解程序,进程,线程的区别呢?

程序:程序是静态的说法,它是保存在某种介质(磁盘)上的可执行文件,是code与data的集合。

进程:处于执行状态的程序以及它所包含的资源的总称,进程是一个程序的动态的执行的实体

线程:线程是进程中的一个动态对象,是程序执行的最小单元

一个进程中可能包含有多个线程,这些线程会共享同一个进程中的资源。例如地址空间,所打开的文件等。

从实际的例子来看,假如我们生成了一个可执行的程序,它保存在文件中时,就是程序;

一旦系统开始调度这个程序执行,那么它可以称为一个进程。

在这个进程中可能会有不同的线程,例如很可能存在tcp thread, ip thread,以及其它为了完成某一功能而创建的thread。

由于我们在嵌入式Linux Kernel实际工作中使用的还是线程的概念,因此之后的讨论一概以线程来称呼。

线程描述符  task_struct  (可以参考kernel/include/linux/sched.h)中包含了一个线程相关的所有信息,里面主要描述了线程的状态,虚拟内存空间信息,寄存器和堆栈上下文等。

---- 线程间的关系

在Linux中,所有的线程可以分为3种,Idle线程内核线程用户线程。

其中,Idle线程是特殊的0号线程,它在Linux系统一开始创建的时候就存在,并且0号线程是其他所有线程的父线程。

内核线程是由kernel_thread或kthread_create等特殊的内核线程函数所创建的

在Linux kernel 2.6以后,基本上内核线程都从2号线程do_fork()而来;

用户线程是在用户态下通过C库函数fork()所创建,并且大部分都是从1号线程继承而来;

0号线程 它就是Idle线程,在kernel初始化时(start_kernel函数中)创建.

并且0号线程的描述符,并不是通过fork()过程创建起来的,而是通过INIT_TASK宏(/kernel/arch/arm/init_task.c)静态配置的。

在SMP系统中,每一个CPU都对应一个0号线程,当CPU空闲时,Linux Kernel将调度Idle线程执行,此时Idle线程进入cpu_idle函数,在该函数内部实现sleep睡眠设计,达到省电的功能;

1号线程:在start_kernel->rest_init函数中,系统通过调用 kernel_thread(kernel_init, ...)而创建了1号线程.

当1号线程完成初始化任务后,会通过run_init_process("/sbin/init", ... ) 函数去执行系统目录下的init程序.

此时1号线程就用内核线程转为了用户线程;init程序会继续完成用户态下的各种应用初始化过程;

由于1号线程是在rest_init函数中通过kernel_thread 函数(实际上最后执行了do_fork() 函数)所创建的。

因此1号线程的父线程是0号线程;1号线程最后的显示为"init"。

2号线程在start_kernel->rest_init函数中,系统通过调用kernel_thread(kthreadd, ...)创建了2号线程

2号线程主要承担创建其他内核线程的任务

在后续kernel的执行中,如果调用 kthread_create()函数来创建内核线程,则其父线程均设置为2号线程;

如果调用kernel_thread()函数来创建内核线程,通过一些附加的操作,也可以将父线程修改为2号线程。

因此,在一个规范的Linux系统中,我们可以看到几乎所有的内核线程,其父线程均为2号线程,

它也是0号线程的子线程;2号线程最后显示为"kthreadd"。

内核线程: 在内核态下,通过kernel_thread()或者kthread_create()等API创建的线程,属于内核线程。

由于kthread_create的实现方式,通过该函数创建的内核线程,其父线程均为2号kthreadd线程;

用户线程: 在用户态下,通过pthread_create()或者fork()等C库函数调用所创建的线程。

由于1号线程是第一个用户线程,同时也是启动其他线程的入口;

因此所有的用户线程都可以从父子关系上查看到,它们属于1号线程的子孙线程。

内核线程与用户线程的差别,主要在于:

1.  创建方式:内核线程必须在内核态下,通过kernel_thread()或者kthread_create()等内核函数API所创建;

用户线程必须在用户态下,通过fork()或pthread_create()等C library函数所创建;

2. 运行方式:内核线程只能运行在内核态,它只能调用内核函数,不能调用用户态函数,因此无法使用C library函数;

用户线程可以运行在用户态,也可以通过系统调用,"陷入"内核态中运行,只有在内核态中才能调用内核函数

3. 运行空间:内核线程只能访问到内核态的大于PAGE_OFFSET(3GB)的地址空间;用户态可以使用整个4GB地址空间

此时前3GB的地址空间属于本进程独享,而大于PAGE_OFFSET的内核态空间则属于全体线程共享;

特权指令:一类只能在核心态下运行而不能在用户态下运行的特殊指令。

不同的操作系统特权指令会有所差异,但是一般来说主要是和硬件相关的一些指令。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值