处理器加电后和断电前,程序计数器会假设一个值的序列:其中,每个
是某个相应的指令
的地址,每次从
到
的过渡称为控制转移(control transfer)。这样的控制转移序列叫做处理器的控制流(flow of control or control flow) control flow的突变(
和
)的不连续,通常由类似跳转、调用、和返回等程序指令造成。突变是现代系统应对系统状态变化的机制,这些突变称为异常控制流(Exceptional Control Flow, ECF)。
异常(exception)是控制流中的突变,一部分由硬件实现,一部分由操作系统实现。当处理器检测有事件发生时,会通过一个叫做异常表(exception table)的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这列事件的操作系统子程序(异常处理程序(exception handler))。当异常处理程序完成处理后,根据引起异常的时间的类型,会发生以下三种情况:
,即当事件发生时正在执行的指令;
,如果没有发生异常将会执行的下一条指令;
系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号(exception number)。其中一些号码是由处理器的设计者分配的,其他号码是由操作系统内核(操作系统常驻内存部分)的设计者分配的,前者的示例包括被零除、缺页、内存访问违例、断点以及算术运算溢出,后者包括系统调用和来自外部I/O设备的信号。系统启动时,操作系统分配和初始化一张称为异常表的跳转表,使得表目k包含异常k的处理程序的地址,如下图:
在运行时,处理器检测到发生了一个事件,并且确定了相应的异常号k。随后处理器触发异常,方法是执行间接过程调用,通过异常表的表目k,转到相应的处理程序,如下图。异常表的起始地址放在一个叫做异常表基址寄存器(exception table base register)的特殊CPU寄存器里。
异常类似于过程调用,但是有一些区别:
中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断不是由任何一条专门的指令造成的,从这个意义上来说它是异步的。图8-5概述了一个中断的处理流程。在当前指令完成执行之后,处理器注意到中断引脚的电压变高了,就从系统总线读取异常号,然后调用适当的中断处理程序,当处理程序返回时,它就将控制返回给下一条指令(即 如果没有发生中断,在控制流中会在当前指令之后的那条指令),结果是程序继续执行,就好像没有发生过中断一样。
陷阱是有意的异常,是执行一条指令的结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序在内核之间提供一个像过程一样的接口,叫做系统调用。用户程序经常需要向内核请求服务,比如读一个文件(read)、创建一个新的进程(fork)、加载一个新的程序(execve),或者终止当前进程(exit)。为了允许对这些内核服务的受控的访问,处理器提供了一条特殊的“syscall n”指令,当用户程序想要请求服务n时,可移植性这条指令。执行syscall指令会导致一个到异常处理程序的陷阱,这个处理程序解析参数,并调用适当的内核程序,如图8-6所示。普通函数调用是在用户模式下,系统调用是在内核模式下。
故障是由错误情况引起的,它可能能被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回给引起故障的指令,从而重新执行它。否则,处理程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序,如图8-7.
eg:缺页异常,当指令引用一个虚拟地址,而与该地址相对应的物理页面不在内存中,因此必须从磁盘中取出时,就会发生故障。详见第九章补上缺页处理程序从磁盘加载适当的页面,然后将控制返回给引起故障的指令,当指令再次执行时,相应的物理页面已经驻留在内存中了,指令就可以没有故障地运行完成了。
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,终止程序将控制返还给一个abort例程,该例程会终止这个应用程序。
异常是允许操作系统内核提供进程(process)概念的基本构造块。进程的经典定义:一个执行中的程序的实例。系统中的每个程序都运行在某个进程的上下文(context)中,上下文是由程序正确运行所需的状态组成,状态包括:存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容,程序计数器、环境变量以及打开文件描述符的集合。每次用户通过向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或者其它应用程序。
单步执行程序,看到的一系列程序计数器(PC)的值(这些值唯一地对应于包含在程序的可执行目标文件中的指令,或者包含在运行时动态链接到程序的共享对象中的指令),这个PC值的序列叫做逻辑控制流,或者简称控制流。如下图,进程是轮流使用处理器的,每个进程执行它的流的一部分,然后被抢占(preempted)(暂时挂起),然后轮到其它进程。
一个逻辑流的执行在时间上与另一个流重叠,称为并发流(concurrent flow),这两个流被称为并发地运行。更准确地说,流X和流Y互相并发,当且仅当X在Y开始之后和Y结束之前开始开始,或者Y在X开始之后和X结束之前开始。8-12中,A和B是并发,A和C是并发,B和C不是并发。多个流兵法的执行的一般现象被称为并发(concurrency)。一个进程和其他进程轮流运行的概念称为多任务(multitasking)。一个进程执行它的控制流的一部分的每一时间段叫做时间片(time slice)。如果两个流并发地运行在不同的处理器核或者计算机上,称为并行流(parallel flow)
进程为每个程序提供它自己的私有地址空间,一般来说和这个空间中某个地址相关联的那个内存字节是不能被其它进程读或者写的,从这个意义上说,这个地质空间是私有的。尽管和每个私有地址空间相关联的内存的内容一般是不同的,但是每个这样的空间都有相同的通用结构,如8-13。
为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。处理器通常是用某个控制寄存器中的一个模式位(mode bit)来提供这种功能,该寄存器描述了进程当前享有的特权。当设置了模式位,进程就运行在内核模式中(有时也叫作超级用户模式)。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令(privileged instruction),比如停止处理器,改变模式位,或者发起一个I/O操作。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。运行应用程序代码的进程初始时是在用户模式中。进程从用户模式变为内核模式的唯一方法是通过注入中断、故障或者陷入系统调用这样的异常。Linux提供了一种叫做/proc文件系统的机制,允许用户模式进程访问内核数据结构的内容。/proc文件系统将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。比如,可以使用文件系统找出一般的系统属性,比如CPU类型(、proc/cpuinfo)。或者某个特殊的进程使用的内存段。chatgpt对/proc的介绍如下:
`/proc` 是一个虚拟文件系统,也被称为进程文件系统,它存在于内存中而不是硬盘上。在 Linux 系统中,`/proc` 目录包含了大量关于系统和正在运行的进程的实时信息。它是一个接口,通过这个接口,内核可以向用户空间程序提供信息。
以下是 `/proc` 中的一些常见文件和目录:
- `/proc/cpuinfo`:包含了处理器的相关信息,如型号、MHz、缓存大小等。
- `/proc/meminfo`:提供了关于系统内存使用情况的信息,包括物理内存、交换空间等。
- `/proc/version`:显示了系统的版本信息。
- `/proc/pid`:每一个正在运行的进程都会在 `/proc` 下有一个以其进程 ID 命名的目录。这个目录中包含了该进程的相关信息。
- `/proc/filesystems`:列出了系统支持的文件系统类型。
- `/proc/mounts`:显示了当前系统挂载的所有文件系统。
- `/proc/net`:包含了网络协议的统计信息。
通过阅读和分析 `/proc` 中的文件,我们可以了解到系统和进程的许多信息。这对于系统监控、调试和性能调优等任务非常有用。
每个进程都有一个唯一的正数(非零)进程ID(PID)。getpid函数返回调用进程的PID,getppid函数返回它的父进程的PID(创建调用进程的进程)
SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU都是什么:
以下是这些信号的解释:
- SIGSTOP:这个信号会让接收它的进程停止运行。这是一个不能被阻塞、处理或者忽略的信号。
- SIGTSTP:这个信号通常由用户在终端中按下 Ctrl+Z 发送,导致进程停止运行,但是与 SIGSTOP 不同的是,这个信号是可以被捕获和忽略的。
- SIGTTIN:当一个后台进程试图读取终端输入时,这个信号会被发送到该进程。默认情况下,这会导致进程停止运行。
- SIGTTOU:当一个后台进程试图写入它的控制终端或者改变终端的模式时,这个信号会被发送到该进程。默认情况下,这会导致进程停止运行。
这些信号通常用于实现 Unix 系统的工作控制,例如将一个正在运行的进程暂停并放到后台,或者将一个在后台暂停的进程恢复运行并放到前台。
当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收(reaped)。当父进程回收已终止的子进程时,内核将紫禁城的退出状态传递给父进程,然后抛弃已终止的进程,从此时开始,该进程就不存在了。一个终止了但还未被回收的进程称为僵死进程(zombie) 如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养父。init进程的PID=1,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止。默认情况下(option=0),waitpid挂起调用进程的执行,直到它的等待集合(wait set)中的一个子进程终止。如果等待集合中的一个进程在刚调用的时刻就已经终止了,那么waitpid就立即返回。在这两种情况中,waitpid返回导致waitpid返回的已终止子进程的PID,此时,已终止的子进程已经被回收,内核会从系统中删除掉它的所有痕迹。参数说明:
一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件,允许进程和内核中断其他进程。每种信号类型都对应于某种系统事件,低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常,异常表如下
传送一个信号到目的进程是由两个不同的步骤组成的:
一个发出而没有被接收的信号叫做待处理信号(pending singal)。在任何时刻,一种类型至多只会有一个待处理信号。如果一个进程有一个类型为k的待处理信号,那么任何接下来发送到这个进程的类型为k的信号都不会排队等待;他们只是被简单地丢弃。一个进程可以选择性地阻塞接收某种信号。当一种信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接受,直到进程取消对这种信号的阻塞。一个待处理信号最多只能被接受一次,内核为每个进程在pending位向量中维护着待处理信号的集合,而在blockerd位向量中维护着被阻塞的信号集合。只要传送了一个类型为k的信号,内核就会设置pending中的第k位,而只要接收了一个类型为k的信号,内核就会清除pending中的第k位。
1.进程组 每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。getpgrp函数返回当前进程的进程组ID。默认的,一个子进程和它的父进程同属于一个进程组。一个进程可以通过使用setpgid函数来改变自己或者其他进程的进程组。setpgid 函数是在 Linux/UNIX 系统下用于设置某个进程的进程组 ID 的,它的函数原型如下:
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
其中,pid 表示需要设置的进程 ID,pgid 表示需要设置的进程组 ID。setpgid 函数可以将一个进程设置为所指定的进程组中的一个成员,同时可以创建新的进程组。使用 setpgid 函数创建新的进程组时,若 pid 参数所指的进程尚未加入任何进程组,则可将其作为新进程组的组长进程(即进程组 ID 与该进程 ID 相同),成功时返回 0,失败时返回 -1。使用 setpgid 函数还可以实现进程的前后台切换。在 Linux/UNIX 系统中,每个终端都有一个唯一的进程组 ID,在某个终端上运行着的进程都属于该终端的进程组。一个进程组可以拥有多个进程。在前台运行的进程接收键盘输入信号,处于后台运行的进程则接收不到键盘输入信号。因此通过将进程的进程组 ID 设置为当前终端的进程组 ID,可以将其放到前台运行,在当前终端接受键盘输入信号。使用 setpgid 函数还可以实现进程的作业控制,例如将多个进程放在同一作业中,并对该作业进行统一管理。2.用/bin/kill 程序发送信号 /bin/kill程序可以向另外的进程发送任意的信号。3.从键盘发送信号 Unix shell 使用作业(job)这个抽象概念来表示对一条命令行求值而创建的进程。在任何时刻,至多只有一个前台作业和0个或多个后台作业。比如键入:
linux > ls | sort
会创建一个一个由两个进程组成的前台作业。这两个进程通过Unix管道连接:一个进程运行ls程序,一个进程运行sort程序。shell为每个作业创建一个独立的进程组。进程ID通常取自作业中父进程中的一个。比如,下面展示了有一个前台作业和两个后台作业的shell。前台作业中的父进程PID为20,进程组ID也是20.父进程创建两个子进程,每个也都是进程组20的成员。
在键盘上输入Ctrl+C会导致内核发送一个SIGINT信号到前台进程组中的每个进程。默认情况下,结果是终止前台作业。类似的,输入Ctrl+Z会发送一个SIGTSTO信号到前台进程组中的每个进程。默认情况下,结果是停止(挂起)前台作业。4.用kill函数发送信号 进程通过调用kill函数发送信号给其他进程(包括自己) 下面展示了父进程用kill函数发送SIGKILL信号给它的子进程。
5.用alarm函数发送信号 进程可以通过调用alarm函数向他自己发送SIGALRM信号。alarm函数安排内核在secs后发送一个SIGALRM信号给调用进程,如果secs=0,则不会调度安排新的闹钟(alarm)。
当内核把进程p从内核模式切换到用户模式时(eg:从系统调用返回或是完成了一次上下文切换),它会检查进程p的未被阻塞的待处理信号的集合(pending & ~blocked)。如果这个集合为空(通常情况下),那么内核将控制传递到p的逻辑控制流中的下一条指令。如果集合是非空的,那么内核将控制传递到p的逻辑控制流中的下一条指令,并且强制p接受信号k。收到这个信号会触发进程采取某种行为。一旦进程完成了这个行为,那么控制就传递回p的逻辑控制流中的下一条指令(
)。每个信号类型都有一个预定义的默认行为,是下面的一种:
进程可以通过signal函数修改和信号相关联的默认行为,唯一的例外是SIGSTOP和SIGKILL,其默认行为不能修改 signal函数可以通过下列三种方法之一来改变和信号signum相关联的行为:
当一个进程捕获了一个类型为k的信号时,会调用为信号k设置的处理程序,一个整数参数被设置为k,这个参数允许同一个处理函数捕获不同类型的信号。当处理程序执行它的return语句时,控制(通常)传递回控制流中进程被信号接收中断位置处的指令。eg:8-30,修改了Ctrl-Z的默认行为
信号处理程序可以被其他信号处理程序中断,如下。
Linux提供阻塞信号的隐式和显式机制
sigprocmask 函数的标准签名如下:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
其中,how 参数指定了操作方式,可以是以下几个常量中的一个:
set参数是一个指向 sigset_t 类型的指针,该类型是一个位向量表示信号集。如果设置信号屏蔽字,set指向的实际位向量中包含的是屏蔽信号的位,未屏蔽的信号的位则被清除。如果您想阻止信号,则需要修改该信号的位状态。另外,oldset 参数是一个输出参数,它记录了之前当前进程的信号屏蔽字状态。例如,下面的代码存储了当前进程的信号屏蔽字并禁用 SIGINT 信号:
#include <signal.h>
int main() {
sigset_t newmask, oldmask;
// 初始化屏蔽字
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
// 设置屏蔽字,保存旧值
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
// 执行某些需要屏蔽信号的操作
// 恢复旧屏蔽字
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return 0;
}
上面的代码首先创建一个新信号屏蔽字 newmask,它将 SIGINT 信号阻止。然后调用 sigprocmask 函数阻止了该信号,并存储了旧的屏蔽字。在此之后,执行需要被屏蔽信号的操作,然后恢复旧的信号屏蔽字,让 SIGINT 信号回到原始的可被处理状态。
处理程序难以推理分析的原因:
eg:考虑图8-39的程序,它总结了一个典型的Unix shell的结构。父进程在一个全局作业列表中记录着它的当前子进程,每一个作业一个条目。addjob和deletejob函数分别向这个作业列表添加和从中删除作业。当父进程创建一个新的子进程后,它就把这个子进程添加到作业列表中。当父进程在SIGCHLD处理程序中回收一个终止的子进程时,它就从作业列表中删除这个子进程。问题是,可能会发生这样的事情:
因此,对于父进程的main程序和信号处理流的某些交错,可能会在addjob之前调用deletejob。这会导致作业列表出现一个不正确的条目,对应于一个不再存在而且永远也不会被删除的作业。另一方面,也有一些交错,事件按照正确的顺序发生。eg:如果在fork调用返回时,内核刚好调度父进程而不是子进程运行,那么父进程就会正确地把子进程添加到作业列表中,然后子进程终止,信号处理函数把该作业从列表中删除。
这是一个称为竞争(race)的经典同步错误的示例。在这个情况下,main函数中调用addjob和处理程序中调用deletejob之间存在竞争。如果addjob赢,则结果是正确的,反之则出错。图8-40展示了消除图8-39中竞争的一种方法,通过在调用fork之前,阻塞SIGCHLD信号,然后在调用addjob之后,取消阻塞。
图8-41给了一个基本思路。父进程设置SIGINT和SIGCHLD的处理程序,然后进入一个无限循环。它阻塞SIGCHLD信号,避免父进程和子进程之间的竞争。创建了子进程之后,把pid重置为0,取消阻塞SIGCHLD,然后以循环的方式等待pid变为非零。子进程终止后,处理程序回收它,把它非零的PID赋值给全局pid变量,终止循环。
上诉代码的问题是循环浪费资源,有以下解决办法:
sigsuspend 是一个 UNIX 系统调用,用于暂停进程并等待信号。当进程执行 sigsuspend 时,它会阻塞,直到收到指定信号之一为止。这个系统调用通常用于等待异步事件,比如定时器或 Socket 等待连接或数据。
sigprocmask 和 sigaction 是 sigsuspend 的前置条件,这些系统调用可用于管理信号处理程序和控制信号的接收。在一些情况下,进程可能需要暂停其信号处理器以等待特定信号,这时就需要使用 sigsuspend。
图8-42是使用sigsuspend的例子。
C提供了一种用户级异常控制流形式,称为非本地跳转(nonlocal jump),它将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要经过正常的调用-返回序列,非本地跳转是通过setjmp和longjmp函数来提供的。
setjmp 是一个 C 语言标准库函数,它允许程序在任意位置保存当前上下文,并在后续恢复该上下文。它通常用于实现非局部跳转(longjmp)。
当调用 setjmp 时,它会保存当前的 CPU 上下文,包括栈指针、程序计数器和寄存器等信息。然后它会返回 0。此后,如果调用 longjmp 并传入相同的 jmp_buf 参数,程序将会回到 setjmp 调用的位置,并且 setjmp 的返回值将为非零值。
setjmp 的返回值和传递给 longjmp 的非零值之间不存在显式的联系。通常,开发者会使用 setjmp 的返回值作为一个状态指示器,用来判断程序是从 setjmp 返回的还是从 longjmp 跳转回来的。
一个常见的用途是处理异常。当程序发生错误需要跳转出去时,可以使用 setjmp 保存当前环境,然后在异常处理函数中使用 longjmp 返回到之前保存的环境。这样就可以避免使用一些过于复杂的结构来处理异常。
longjmp 是一个 C 语言标准库函数,用于进行非局部跳转。与 goto 不同的是,longjmp 可以跳出多层函数调用,返回到调用 setjmp 时保存的上下文状态。
当调用 longjmp 时,会使程序跳转到之前调用过 setjmp 保存的上下文状态。longjmp 的第二个参数指定跳转时的返回值。
longjmp 的使用必须在 setjmp 的作用域范围内。如果 longjmp 跳转到了不在 setjmp 的作用域内的位置,程序的行为将是未定义的。
由于 longjmp 的跳转是非常强大和危险的,因此它通常被用于处理异常情况,当程序遇到意外错误时,使用 setjmp 在当前位置进行状态保存,然后在异常处理函数中使用 longjmp 跳转到保存的状态进行处理。 但是,使用 setjmp 和 longjmp 时需要非常小心,确保它们被正确使用,否则会导致程序崩溃或产生其他严重问题。
图8.43展示了一个非本地跳转的例子。非本地跳转的一个重要应用是允许从一个深层嵌套的函数调用中立即返回,通常是由检测到某个错误情况引起的。如果在一个深层嵌套的函数调用中发现了一个错误情况,我们可以使用非本地跳转直接返回到一个普通的本地化的错误处理程序,而不是费力地解开调用栈。
longjmp允许它跳过所有中间调用的特性可能产生意外的后果。eg:如果中间函数调用中分配了某些数据结构,本来预期在函数结尾处释放它们,那么这些释放代码会被跳过,从而产生内存泄漏。
非本地跳转的另一个重要应用是使一个信号处理程序分支到一个特殊的代码位置,而不是返回到被信号到达中断了的指令的位置。图8-44展示了一个简单的技术,说明了这种基本技术:当用户在键盘上键入Ctrl + C时,这个程序用信号和非本地跳转实现软重启。sigsetimp和siglongjmp是setjmp和longjmp的可以被信号处理程序使用的版本。在程序第一次启动时,对sigsetimp函数的初始调用保存调用环境和信号的上下文(包括待处理的和被阻塞的信号向量)。随后,主函数进入一个无限循环。当用户键入Ctril + C时,内核发送一个SIGINT信号给这个进程,该进程捕获这个信号。不是从信号处理程序返回,而是实现一个非本地跳转,回到main函数的开始处。两个注意点:
strace 是一种 Linux 系统级调试工具,它可以跟踪和记录应用程序执行时所有系统调用和信号,以便分析和调试程序。它可以用来解决一些问题,例如程序运行慢、程序崩溃或挂起等。
使用 strace 可以获取程序在执行时调用系统库的详细信息和调用顺序,例如文件操作、网络操作、进程管理等,可以显示系统调用的返回值、参数以及调用时间等详细信息。通过查看 strace 输出可以判断程序是否正常执行,是否有权限问题或者其他异常。
strace 的使用非常简单,只需要在命令前加上 strace 即可,例如:
strace ls -l
这条命令会跟踪 ls -l 命令执行时所有的系统调用和信号,并将结果输出到标准输出。
除了常见的选项,例如 -o 参数可以将输出保存到文件中,-p 参数可以指定要跟踪的进程 ID,-f 参数可以跟踪子进程等。strace 是 Linux 系统中非常有用的调试工具之一。
ps 是一个常用的 Linux 命令,用于查看系统进程信息。ps 命令可以显示与当前登录用户有关的所有进程,或者指定进程的信息。你可以使用 ps 命令来监控系统运行状态,或者找到某个进程的 PID(进程ID)。
ps 命令的常用选项包括:
ps aux 列出所有进程的详细信息,包括进程的 PID、占用 CPU 的百分比、占用内存的百分比、启动时间、命令及其参数等。
ps -f 以完整格式显示进程信息。
ps -ef 以完整格式显示所有进程信息,包括命令行参数,使用 UID 和 GID 显示所有者和组。
ps -e 列出所有正在运行的进程。
ps -C command_name 显示指定命令的进程信息。
ps -p pid 显示指定进程的信息。
ps -t terminal 仅显示运行在指定终端上的进程。
其中 aux 和 ef 是最常用的选项,可以显示最详细的进程信息。例如:
ps aux
该命令会列出当前系统上所有的进程信息,并展示详细的配置信息,让你很容易找到正在运行的进程信息和相应的 PID。
ps -h 是 ps 命令的一个选项,它的作用是隐藏 ps 命令输出中的标题行。这意味着当你使用 ps -h 命令时,你只会看到进程信息,而不会看到默认的标题行。
top 是一个命令行实用程序,它可以在类 Unix 系统中可视化地显示进程活动和系统资源的使用情况。它具有实时监视的功能,可以帮助管理员了解系统的性能,并能够及时响应问题。
当你运行 top 命令时,它会向你展示一个实时更新的进程列表,列表按照 CPU 使用率或内存消耗来排序。默认情况下,列表按照 CPU 使用率排序,最先显示最消耗 CPU 的进程。你可以按下不同的键来切换列表的排序方式。
top 命令还会显示系统的负载情况(即 CPU 利用率)、内存使用情况、交换空间使用情况、进程数量等信息。它还会提供一些基本的交互式功能,例如在进程列表中选择进程来终止它们,修改进程的优先级等。
pmap 是一个命令行实用程序,它可以显示指定进程或进程 ID 的内存映射情况。它通常用于诊断和调试进程的内存使用情况,包括进程占用的内存大小、内存区域的地址范围、内存映射文件、共享库等信息。
当你运行 pmap 命令时,可以看到如下输出:
$ pmap 1234
1234: /usr/bin/python3 myscript.py
0000555555554000 48K r-x-- myscript.py
0000555555571000 2048K ----- myscript.py
00007ffff7a38000 1408K r---- libc-2.27.so
00007ffff7bd5000 1048K r-x-- libc-2.27.so
00007ffff7cf3000 24K r---- libc-2.27.so
00007ffff7cf9000 8K rw--- libc-2.27.so
00007ffff7cfb000 16K rw--- [ anon ]
00007ffff7cff000 144K r-x-- ld-2.27.so
00007ffff7ee4000 12K rw--- [ anon ]
00007ffff7eee000 8K rw--- [ anon ]
00007ffff7ef0000 4K r---- ld-2.27.so
00007ffff7ef1000 4K rw--- ld-2.27.so
00007ffff7ef2000 4K rw--- [ anon ]
00007ffff7ef3000 4K r---- myscript.py
00007ffff7ef4000 4K rw--- myscript.py
00007ffffffde000 132K rw--- [ stack ]
ffffffffff600000 4K r-x-- [ anon ]
---------------- ------
total 3896K
输出中分别列出了进程中的内存区域,并显示了每个区域的地址范围、权限、来源以及占用的内存大小。在这个例子中,pmap 命令显示了进程 ID 为 1234 的 Python 进程的内存映射情况,其中还包括 Python 解释器使用的一些共享库和内存区域。你可以使用 pmap 命令来确定内存使用情况、查找内存泄漏或者优化进程占用的内存等。
通过读取 /proc 目录中的文件,可以获得有关系统和进程状态的各种信息。
例如,读取 /proc/cpuinfo 文件可以获得有关 CPU 型号、频率、核心数和缓存等信息。读取 /proc/meminfo 文件可以获得有关系统内存使用情况的信息。读取 /proc/[pid]/status 文件可以获得特定进程的状态信息,如进程 ID、用户名、运行状态、内存使用情况等。
/proc 目录下的文件和目录通常都是只读的,但在某些情况下也可以进行写入。例如,向 /proc/sys/kernel/hostname 文件中写入一个新的主机名可以更改系统的名称。此外,通过在 /proc/sys 目录中进行写入,可以更改系统内核的一些参数和配置。
总之,/proc 为我们提供了一种查看和更改系统状态、进程状态和内核参数的方法。但是需要注意的是,在读取和写入 /proc 目录下的文件时,需要有足够的权限才可以进行操作。
https://github.com/liuxubit/csapp_labs/tree/shlab
https://gitee.com/sun-hongwei8011/csapp-lab
本文分享自 GiantPandaCV 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!