首页  >  文章  >  系统教程  >  深入了解进程:查看子进程、线程信息及常见状态

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

PHPz
PHPz原创
2024-07-25 08:00:14333浏览

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

Q&A

ps 모든 하위 프로세스를 보시겠습니까? pstree-ppid

ps-eLf 각 배열의 의미는 무엇인가요?

으아아아

프로세스에 속한 모든 스레드의 CPU 사용률/비디오 메모리/우선순위 및 기타 정보를 확인하시겠습니까? 탑-H-p25120

스레드가 어떤 CPU에서 실행되고 있는지 확인하세요. ps-eoruser,pid,ppid,lwp,psr,args-L|grepl3-agent

공통 프로세스 상태?

으아아아

프로세스 및 변환 프로세스(라이프 사이클)의 6가지 상태 - 준비 상태, 실행 중 상태, 깊은 수면 상태, 가벼운 수면 상태, 정지 상태, 좀비 상태.

모자 프로세스는 어떻게 공유되나요? 차이점은? ——fork 후 모 프로세스와 자식 프로세스는 mmap으로 구성된 파일 디스크립터와 매핑 영역을 공유하지만 나머지는 복사되지만 프로세스 공간과 프로세스 주소까지 상위 프로세스와 완전히 동일하므로 완전히 독립된 공간)

fork와 vfork의 차이점 - vfork는 새로운 프로세스를 생성하는데 사용되며, 새로운 프로세스의 목적은 새로운 프로그램을 실행하는 것입니다. 차이점 1: 상위 프로세스의 주소 공간이 하위 프로세스에 복사되지 않습니다. 차이점 2: vfork는 exec 또는 (exit)를 호출한 후 하위 프로세스가 먼저 실행되도록 보장하며 상위 프로세스가 실행되도록 예약할 수 있습니다.

**공유 비디오 메모리의 주소 값이 사용자 프로세스 공간 범위 내에 있나요? **포크 후 하위 프로세스는 상위 프로세스와 동일한 주소를 사용합니까? 서로 다른 프로세스인 경우 공유 비디오 메모리의 프로세스 공간에 표시되는 주소는 동일합니까?

프로세스의 수명 주기, 준비 상태 및 실행 상태는 수치적으로 동일하며 TASK_RUNNING 매크로에 의해 정의됩니다.

Task_struct는 Linux에서 어떻게 관리되나요?

좀비 프로세스를 이해하세요. 거의 모든 비디오 메모리 공간을 포기하고 실행 가능한 코드가 없으며 예약할 수 없습니다. 프로세스 목록의 위치만 유지합니다(리소스는 오랫동안 해제되었으며 Task_struct 구조는 여전히 존재합니다). . 상위 프로세스가 SIGCHLD 신호 처리 기능을 설치하지 않고 wait()/waitpid()를 호출하여 하위 프로세스가 끝날 때까지 기다리며 명시적으로 신호를 무시하지 않으면 여전히 좀비 상태로 유지됩니다. 상위 프로세스가 종료되면 init 프로세스가 수동으로 이 하위 프로세스를 인계받아 LINUX 커뮤니티에서 정리합니다. 여전히 제거될 수 있습니다. 상위 프로세스가 종료되지 않으면 하위 프로세스는 좀비 상태로 유지되기 때문에 시스템에 좀비 프로세스가 많이 존재하는 경우가 있습니다. 시스템에서 사용할 수 있는 프로세스 수는 제한되어 있습니다(cat/proc/sys/kernel/pid_max). 좀비 프로세스가 많이 형성되면 사용 가능한 프로세스가 없어 시스템이 새로운 프로세스를 형성할 수 없게 됩니다. 숫자.

하위 프로세스 재활용:

상위 프로세스는 wait/waitpid 및 기타 기능을 통해 하위 프로세스가 끝날 때까지 기다립니다. 이로 인해 상위 프로세스가 중단됩니다. 상위 프로세스가 매우 바쁜 경우 신호 기능을 사용하여 SIGCHLD에 대한 핸들러를 설치할 수 있습니다. 하위 프로세스가 종료된 후 상위 프로세스가 신호를 수신하고 재활용을 위해 핸들러에서 대기를 호출할 수 있기 때문입니다. 부모 프로세스가 자식 프로세스가 끝나는 시점을 신경 쓰지 않으면 신호(SIGCHLD, SIG_IGN)를 사용하여 자식 프로세스가 끝나는 데 관심이 없다는 것을 커널에 알릴 수 있으며, 커널은 이를 재활용합니다. 더 이상 부모 프로세스에 메시지를 보내지 않습니다. 즉, 두 번 포크하는 방법도 있습니다. 부모 프로세스는 자식 프로세스를 포크한 다음 계속 작동합니다. 즉, 자식 프로세스는 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)|

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

|堆向高地址下降|

||

||

|自由空间|

||

||

|栈向低地址下降|

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

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

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn