深入了解进程:查看子进程、线程信息及常见状态

Q&A

ps查看所有子进程?pstree-ppid

ps-eLf各数组涵义?

PID:process id 进程id
PPID: parent process id 父进程id
LWP:表示这是个线程;要么是主线程(进程),要么是线程
NLWP: num of light weight process 轻量级进程数量,即线程数量
TIME: 占用的CPU总时间
C CPU利用率,以整数表示。

登录后复制

查看进程下的所有线程cpu借助率/显存/优先级等信息?top-H-p25120

查看线程在那个CPU上运行?ps-eoruser,pid,ppid,lwp,psr,args-L|grepl3-agent

常见的进程状态?

-D:不可被唤醒的睡眠状态,通常用于 I/O 情况。
-R:该进程正在运行。
-S:该进程处于睡眠状态,可被唤醒。
-T:停止状态,可能是在后台暂停或进程处于除错状态。
-W:内存交互状态(从 2.6 内核开始无效)。
-X:死掉的进程(应该不会出现)。
-Z:僵尸进程。进程已经中止,但是部分程序还在内存当中。
-<:高优先级(以下状态在 BSD 格式中出现)。
-N:低优先级。
-L:被锁入内存。
-s:包含子进程。
-l:多线程(小写 L)。
-+:位于后台。

登录后复制

进程的六种状态及其转换过程(生命周期)——就绪态,运行态,深度睡眠态,浅睡眠态,停止态,僵尸态。

母子进程共享的是?不同的是?——fork后,母子进程共享有(文件描述符、mmap构建的映射区。其他都是复制了一份,只是该进程的进程空间甚至进程地址完全跟父进程看着一样,而且完全是独立的空间了)

fork与vfork的区别——vfork用于创建一个新进程,而该新进程的目的是exec一个新程序。区别一:不将父进程的地址空间复制到子进程。区别二:vfork保证子进程先运行,在它调用exec或(exit)然后父进程才可能被调度运行。

**共享显存的地址值是在用户进程空间范围之内么?**fork以后子进程使用该地址和父进程是一样的么?若果是不同的进程,共享显存的进程空间中显示的地址是一样的么?

进程的生命周期,就绪态和运行态在数值上是相等的,都是由宏TASK_RUNNING定义。

Linux中Task_struct是怎样被管理的呢?

理解僵尸进程——放弃了几乎所有显存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置(资源早已释放,Task_struct结构还在)。假如他的父进程没安装SIGCHLD讯号处理函数调用wait()/waitpid()等待子进程结束,又没有显式忽视该讯号,这么它就仍然保持僵尸状态,假如父进程结束了,这么init进程手动会接手这个子进程,为它收尸LINUX社区,它还是能被消除的。假如父进程不会结束,子进程都会仍然保持僵尸状态,这就是为何系统中有时会有好多的僵尸进程。系统所能使用的进程号是有限的(cat/proc/sys/kernel/pid_max),假如大量的形成僵死进程,将由于没有可用的进程号而造成系统不能形成新的进程。

子进程回收:

父进程通过wait/waitpid等函数等待子进程结束,这会造成父进程挂起。假如父进程很忙,这么可以用signal函数为SIGCHLD安装handler,由于子进程结束后,父进程会收到该讯号,可以在handler中调用wait回收。假如父进程不关心子进程哪些时侯结束,这么可以用signal(SIGCHLD,SIG_IGN)通知内核,自己对子进程的结束不感兴趣,这么子进程结束后,内核会回收,并不再给父进程发送讯号还有一些方法,就是fork两次,父进程fork一个子进程,之后继续工作,子进程fork一个孙进程后退出,这么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。

子进程结束后为何要步入僵尸状态?——因为父进程可能要取得子进程的退出状态等信息。

僵尸状态是每位子进程必经的状态吗?——任何一个子进程(init除外)在exit()以后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构(它占用一点显存资源,也就是进程表里还有一个记录),等待父进程处理。如果子进程在exit()以后,父进程没有来得及处理,这时用ps命令才能看见子进程的状态是“Z”。假如父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身分对僵尸状态的子进程进行处理。

僵尸进程消除的方式:

改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD讯号。子进程死后,会发送SIGCHLD讯号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。这是基于这样的原理:即使父进程没有调用wait,内核也会向它发送SIGCHLD消息,虽然对的默认处理是忽视,假如想响应这个消息,可以设置一个处理函数。SIGCHLD讯号:子进程结束时,父进程会收到这个讯号。假如父进程没有处理这个讯号,也没有等待(wait)子进程,子进程其实中止,而且都会在内核进程表中占有表项,这时的子进程称为僵尸进程。此类情况我们应当避开(父进程或则忽视SIGCHILD讯号,或则捕捉它,或则wait它派生的子进程,或则父进程先中止,这时子进程的中止手动由init进程来接管)。

进程中止时exit()函数,这么线程中止是哪些呢?——线程中止的三种情况:

线程只是从启动函数中返回,返回值是线程的退出码。线程可以被同一进程中的其他线程取消。线程调用pthread_exit。

倘若不等待一个线程,同时对线程的返回值不感兴趣,可以设置这个线程为被分离状态,让系统在线程退出的时侯手动回收它所占用的资源。一个线程不能自己调用pthread_detach改变自己为被分离状态,只能由其他线程调用pthread_detach。

pthread_cancel()容许一个线程取消th指定的另一个线程。

进程——资源分配的最小单位,线程——程序执行的最小单位。进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间),一个进程崩溃后,在保护模式下不会对其它进程形成影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程挂掉就等于整个进程跑掉,所以多进程的程序要比多线程的程序强壮,但在进程切换时,花费资源较大,效率要差一些。但对于一些要求同时进行而且又要共享个别变量的并发操作,只能用线程,不能用进程。

使用多线程的理由?

进程

进程是资源分配的基本单位,线程是调度的基本单位

进程信息

Linux调度器实际是辨识task_struct进行调度。无论进程线程,底层都对应一个task_struct,进程和线程的区别是共享资源的多少,两个进程间完全不共享资源,两个线程间共享所有资源。

PCB(ProcessControlBlock)进程控制块

task_struct就是Linux内核对于一个进程的描述,也可以称为「进程描述符」。储存着进程所须要的所有资源的结构的描述。/proc/${pid}进程相关信息。对于操作系统,进程就是一个数据结构。

struct task_struct {
longstate; // 进程状态-1为不可运行, 0为可运行, >0为已中断

struct mm_struct*mm; // 指向的是进程的虚拟内存,也就是载入资源和可执行文件的地方
	pid_t pid; // 进程标识符,用来代表一个进程
 
struct task_struct __rcu*parent; // 指向父进程的指针
struct list_headchildren; // 子进程列表
 	struct list_head sibling; // 兄弟进程
struct fs_struct*fs;// 存放文件系统信息的指针
 
struct files_struct *files; // 一个数组,包含该进程打开的文件指针
unsigned int policy; // 调度策略:一般有FIFO,RR,CFS
...
};

登录后复制

从2.6版本之后,Linux改用了slab分配器动态生成task_struct,只须要在栈底(向上下降的栈)或栈顶(向下下降的栈)创建一个新的结构structthread_info(这儿是栈对象的尾端),你可以把slab分配器觉得是一种分配和释放数据结构的优化策略。通过预先分配和重复使用task_struct,可以防止动态分配和释放带来的资源消耗。

进程的地址空间ref

所谓进程地址空间(processaddressspace),就是从进程的视角听到的地址空间,是进程运行时所用到的虚拟地址的集合。

进程的显存

程序段(Text):程序代码在显存中的映射,储存函数体的二补码代码。

初始化过的数据(Data):在程序运行初早已对变量进行初始化的数据。

未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。

栈(Stack):储存局部、临时变量,函数调用时,储存函数的返回表针,用于控制函数的调用和返回。在程序块开始时手动分配显存,结束时手动释放显存,其操作方法类似于数据结构中的栈。

堆(Heap):储存动态显存分配,须要程序员手工分配,手工释放.注意它与数据结构中的堆是两码事,分配方法类似于数组。

注:1.Text,BSS,Data段在编译时早已决定了进程将占用多少VM

可以通过size,晓得这种信息:

正常情况下,Linux进程不能对拿来储存程序代码的显存区域执行写操作,即程序代码是以只读的形式加载到显存中,但它可以被多个进程安全的共享。

创建进程后都创建了什么资源

进程创建

system()通过调用shell启动一个新进程

exec()以替换当前进程映像的方法启动一个新进程

fork()以复制当前进程映像的方法启动一个新进程

fork(2)

执行fork后,父进程的task_struck对拷给子进程,母子进程最初资源完全一样,而且是两份不同的拷贝,因而任何改动都导致两者的分裂。

兄妹进程对显存资源(mm)的管理使用了COW(Copy-On-Write,写时拷贝)技术:

在fork之前,一片显存区对应一份数学地址和一份虚拟地址,显存区的权限为RW;在fork以后,母子进程听到的显存区虚拟地址相同,化学地址也相同,母女进程使用的虽然是同一片化学显存,未发生显存拷贝,操作系统会将此显存区权限改为RO;父或子进程对显存区执行写操作将触发PageFault,操作系统此时会将显存区拷贝一份,母女进程见到的虚拟地址仍旧一样,而且化学地址早已不同。各进程虚拟地址到化学地址的映射由MMU(MemoryManagementUnit,显存管理单元)管理。fork运行在有MMU的CPU上。

对于无MMU的CPU,难以应用COW,难以支持fork。无MMU的CPU使用vfork创建进程,父进程将始终阻塞直至子进程exit或exec。vfork和fork的本质区别是,vfork中的母子进程共用同一片显存区。

fork(2)系统调用用于创建一个新进程,称为子进程,它与父进程同时运行(并发),且运行次序不定(异步)。pid_t是一个宏定义,其实质是int,若成功则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1

父级的整个虚拟地址空间在子进程中复制,包括互斥锁的状态,

子进程与父进程完全相同,不仅以下几点:

进程回收wait()和waitpid()

通过waitpid()/wait()回收子进程的task_struct结构。

区别:

孤儿进程和僵尸进程僵尸进程

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,这么子进程的进程描述符始终保存在系统中。这些进程称之为僵死进程。(子进程可以通过/proc/$pid看见,线程没有)

一个进程在调用exit命令结束自己的生命的时侯,虽然它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程弄成一个僵尸进程,并不能将其完全销毁)。

在Linux进程的状态中,僵尸进程是十分特殊的一种,它早已舍弃了几乎所有显存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程搜集,除此之外,僵尸进程不再占有任何显存空间。它须要它的父进程来为它收尸。

假如他的父进程没安装SIGCHLD讯号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽视该讯号,这么它就仍然保持僵尸状态,假如这时父进程结束了,这么init进程手动会接手这个子进程,为它收尸,它还是能被消除的。

而且假如父进程是一个循环,不会结束,这么子进程都会仍然保持僵尸状态,这就是为何系统中有时会有好多的僵尸进程。系统所能使用的进程号是有限的,假如大量的形成僵死进程,将由于没有可用的进程号而造成系统不能形成新的进程.

僵尸进程形成缘由:子进程结束后向父进程发出SIGCHLD讯号,父进程默认忽视了它;父进程没有调用wait()或waitpid()函数来等待子进程的结束。防止僵尸进程方式:父进程调用wait()/waitpid()等待子进程结束,这样处理父进程通常会阻塞在wait处而不能处理其他事情。捕捉SIGCHLD讯号,并在讯号处理函数上面调用wait函数,这样处理可防止1中描述的问题。fork两次,父进程创建父亲进程,父亲进程再创建一个儿子进程,之后母亲进程自尽,儿子进程成为孤儿进程被init进程收留。孤儿进程

孤儿进程:父进程退出,而它的一个或多个子进程还在运行,这么这些子进程将成为孤儿进程。孤儿进程将被init进程(pid=1)所收留,并由init进程对它们完成状态搜集工作。(假若系统中出现孤儿进程,说明主进程在退出前没有清除子进程)

线程(LightweightProcess,LWP)

同一进程的多个线程获取进程ID时得到的是惟一ID值。Linux同一进程的多线程,在内核视角实际上每位线程都有一个PID,但在用户空间须要getpid()返回惟一值,Linux使用了一个小方法,引入了TGID的概念linux进程与线程 内核,getpid()返回的的TGID值。

pthread_create()

Linux线程本质上就是进程,只是与进程间资源共享方法不同,线程间共享所有资源。每位线程都有自己的task_struct,因而每位线程都可被CPU调度。多线程间又共享同一进程资源。

在一个线程中创建了另外一个线程,主线程要等到创建的线程返回了,获取该线程的返回值后主线程才退出。这个时侯就须要用到线程挂起。pthread_join函数用于挂起当前线程,直到指定的线程中止为止。

说线程的PID,是指用户空间的进程ID,值就是TGID(threadgroupIDforthethreadgroupleader);当非常强调,线程在内核空间的PID,则指线程在内核中task_struct里特有的PID。top–H命令从线程视角显示CPU占用率。不带参数的top命令,进程ID是主线程的PID(也就是TGID)。

Linux的进程和线程

进程是处于运行期的程序和相关资源的统称,具备一些要素:

拥有一段可执行程序代码。如同一场戏须要一个剧本。代码段可以多个进程共用,如同许多场表演都可以用一份剧本一样。拥有一段进程专用的系统堆栈空间。可以觉得是这个进程的“私有财产”,相应的,也肯定有系统空间堆栈在系统中有进程控制块(或称进程描述符,本文两种说法通用)描述这个进程的相关信息。可以觉得是进程的“户口”。系统通过这个控制块来控制进程的相关行为有独立的储存空间,也就是专有的用户空间,相应的又会有用户空间堆栈。理解各类ID

# ps -eo ppid,pid,tid,lwp,tgid,pgrp,sid,tpgid,args -L | awk '{if(NR==1) print $0; if($9~/a.out/) print $0}'
 PPID PID TID LWPTGIDPGRP SID TPGID COMMAND
 579046 2436128 2436128 2436128 2436128 2436128579046 2436128 ./a.out
 579046 2436128 2436129 2436129 2436128 2436128579046 2436128 ./a.out
 579046 2436128 2436130 2436130 2436128 2436128579046 2436128 ./a.out

登录后复制

pidstat-t[-ppid号]可以复印出线程之间的关系。

各类ID最后都归结到pid和lwp(tid)上。所以理解各类ID,最终归结为理解pid和lwp(tid)的联系和区别。

PID:进程ID。

LWP:线程ID。在用户态的命令(例如ps)中常用的显示方法。

TID:线程ID,等于LWP。TID在系统提供的插口函数中更常用,例如syscall(SYS_gettid)和syscall(__NR_gettid)。

TGID:线程组ID,也就是线程组leader的进程ID,等于PID。

pgid(processgroupID):进程组ID,也就是进程组leader的进程ID。

pthreadid:pthread库提供的ID,生效范围不在系统级别,可以忽视。

sid:sessionIDforthesessionleader。

TPGID:ttyprocessgroupIDfortheprocessgroupleader。

上图挺好地描述了用户视角(userview)和内核视角(kernelview)看见线程的差异:

轮询

轮询(用户级线程),这是对内核透明的,也就是系统并不晓得有轮询的存在,是完全由用户的程序自己调度的,由于是由用户程序自己控制,这么就很难像占领式调度那样做到强制的CPU控制权切换到其他进程/线程,一般只能进行协作式调度,须要轮询自己主动把控制权出售出去以后,其他轮询能够被执行到。

goroutine和解释器区别

本质上,goroutine就是轮询。不同的是,Golang在runtime、系统调用等多方面对goroutine调度进行了封装和处理,当遇见长时间执行或则进行系统调用时,会主动把当前goroutine的CPU(P)出售出去,让其他goroutine能被调度并执行linux运维招聘,也就是Golang从语言层面支持了解释器。

其他方面不同进程调度

linux内核的三种主要调度策略:

1,SCHED_OTHER分时调度策略,

2,SCHED_FIFO实时调度策略linux进程与线程 内核,先到先服务

3,SCHED_RR实时调度策略,时间片轮转

实时进程调度

SCHED_FIFO:不同优先级根据优先级高的先挪到睡眠,优先级低的再跑;同等优先级先进先出。

SCHED_RR:不同优先级根据优先级高的先挪到睡眠,优先级低的再跑;同等优先级轮转。

内核RT补丁:

如下两个参数

/proc/sys/kernel/sched_rt_period_us

/proc/sys/kernel/sched_rt_runtime_us

表示在period的时间里RT最多只能跑runtime的时间

普通进程调度

SCHED_OTHER:

CFS:完全公正调度(新内核)

黑红树,右侧节点大于左侧节点的值

运行到目前为止vruntime最小的进程

同时考虑了CPU/IO和nice

总是找vruntime最小的线程调度。

vruntime=pruntime/weight×1024;

vruntime是虚拟运行时间,pruntime是化学运行时间,weight权重由nice值决定(nice越低权重越高),则运行时间少、nice值低的的线程vruntime小,将得到优先调度。这是一个随运行而动态变化的过程。

内核态与用户态内核空间和用户空间

Linux的虚拟地址空间范围为0~4G,Linux内核将这4G字节的空间分为两部份,将最高的1G字节(从虚拟地址0xCxC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)供各个进程使用,称为“用户空间。由于每位进程可以通过系统调用步入内核,因而,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每位进程可以拥有4G字节的虚拟空间。

Linux使用两级保护机制:0级供内核使用,3级供用户程序使用,每位进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的,最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。

内核空间中储存的是内核代码和数据,而进程的用户空间中储存的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。其实内核空间抢占了每位虚拟空间中的最高1GB字节,但映射到化学显存却总是从最低地址(0x00000000),另外,使用虚拟地址可以挺好的保护内核空间被用户空间破坏,虚拟地址到化学地址转换过程有操作系统和CPU共同完成(操作系统为CPU设置好页表,CPU通过MMU单元进行地址转换)。

注:多任务操作系统中的每一个进程都运行在一个属于它自己的显存沙盒中,这个沙盒就是虚拟地址空间(virtualaddressspace),在32位模式下,它总是一个4GB的显存地址块。这种虚拟地址通过页表(pagetable)映射到化学显存,页表由操作系统维护并被处理器引用。每位进程都拥有一套属于它自己的页表。

进程显存空间分布如右图所示:

显存

堆向下下降,栈向上下降(为何我的是相反的呢???)

堆栈、内存的下降方向与大小端

低->|-----------------|

|全局量(所有已初始化量.data,|

|未初始化量.bss)|

堆起始->|-----------------|

|堆向高地址下降|

||

||

|自由空间|

||

||

|栈向低地址下降|

高栈起始->|-----------------|

以上就是深入了解进程:查看子进程、线程信息及常见状态的详细内容,更多请关注小闻网其它相关文章!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。