Linux进程控制:进程信息查询、进程的创建与杀死、进程替换、僵尸进程

进程控制

进程是管理事务的基本单元
进程拥有自己独立的处理环境(如:环境变量,程序运行的目录,哪个用户在运行此程序等)和系统资源(如:处理器 CPU 占用率、存储器、I/O设备、数据、程序)。

进程控制块PCB

Linux内核的进程控制块是task_struct结构体
位于: /usr/src/linux-headers-xxx/include/linux/sched.h

PSB代码很长,以下是部分信息。

  1. 进程id:系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
  2. 进程的状态:有就绪、运行、挂起、停止等状态。
  3. 进程切换时需要保存和恢复的一些CPU寄存器。
  4. 描述虚拟地址空间的信息。
  5. 描述控制终端的信息。
  6. 当前工作目录(Current Working Directory)。
  7. umask掩码。
  8. 文件描述符表,包含很多指向file结构体的指针。
  9. 和信号相关的信息。
  10. 用户id和组id。
  11. 会话(Session)和进程组。
  12. 进程可以使用的资源上限(Resource Limit)。

进程状态

在三态模型中,进程状态分为三个基本状态,即运行态,就绪态,阻塞态。
在五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态。
在这里插入图片描述
TASK_RUNNING:进程正在被CPU执行。当一个进程刚被创建时会处于TASK_RUNNABLE,表示己经准备就绪,正等待被调度。
TASK_INTERRUPTIBLE(可中断):进程正在睡眠(也就是说它被阻塞)等待某些条件的达成。一旦这些条件达成,内核就会把进程状态设置为运行。处于此状态的进程也会因为接收到信号而提前被唤醒,比如给一个TASK_INTERRUPTIBLE状态的进程发送SIGKILL信号,这个进程将先被唤醒(进入TASK_RUNNABLE状态),然后再响应SIGKILL信号而退出(变为TASK_ZOMBIE状态),并不会从TASK_INTERRUPTIBLE状态直接退出。
TASK_UNINTERRUPTIBLE(不可中断):处于等待中的进程,待资源满足时被唤醒,但不可以由其它进程通过信号或中断唤醒。由于不接受外来的任何信号,因此无法用kill杀掉这些处于该状态的进程。而TASK_UNINTERRUPTIBLE状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程,于是原有的流程就被中断了,这可能使某些设备陷入不可控的状态。处于TASK_UNINTERRUPTIBLE状态一般总是非常短暂的,通过ps命令基本上不可能捕捉到。
TASK_ZOMBIE(僵死):表示进程已经结束了,但是其父进程还没有调用wait4或waitpid()来释放进程描述符。为了父进程能够获知它的消息,子进程的进程描述符仍然被保留着。一旦父进程调用了wait4(),进程描述符就会被释放。
TASK_STOPPED(停止):进程停止执行。当进程接收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号的时候。此外,在调试期间接收到任何信号,都会使进程进入这种状态。当接收到SIGCONT信号,会重新回到TASK_RUNNABLE。

stat中参数含义:

参数含义
D不可中断 Uninterruptible(usually IO)
R正在运行,或在队列中的进程
S(大写)处于休眠状态
T停止或被追踪
Z僵尸进程
W进入内存交换(从内核2.6开始无效)
X死掉的进程
<高优先级
N低优先级
s包含子进程
+位于前台的进程组

进程信息查看

ps

常用组合:

ps aux
ps ef
ps -a

看进程的详细状况,常用选项(选项前可以不加“-”)

选项含义
-a显示终端上的所有进程,包括其他用户的进程
-u显示进程的详细状态
-x显示没有控制终端的进程
-w显示加宽,以便显示更多的信息
-r只显示正在运行的进程
-j列出与作业控制相关的信息

top

top命令用来动态显示运行中的进程。
使用top命令时加上-d 来指定显示信息更新的时间间隔

按键含义
M根据内存使用量来排序
P根据CPU占有率来排序
T根据进程运行时间的长短来排序
U可以根据后面输入的用户名来筛选进程
K可以根据后面输入的PID来杀死进程。
q退出
h获得帮助

杀死进程

kill

指定进程ID

kill [-signal] pid

信号值(signal)从0到15,其中9为绝对终止

killall

指定进程名字

killall [-signal] pname

进程号&查询进程号的函数

进程号(PID):非负整型数
父进程号(PPID)
进程组号(PGID):进程组可以接收同一终端的各种信号,默认的情况下,当前的进程号会当做当前的进程组号

getpid()

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
功能:
    获取本进程号(PID)
参数:
    无
返回值:
    本进程号

getppid()

#include <sys/types.h>
#include <unistd.h>

pid_t getppid(void);
功能:
    获取调用此函数的进程的父进程号(PPID)
参数:
    无
返回值:
    调用此函数的进程的父进程号(PPID)

getpgid()

#include <sys/types.h>
#include <unistd.h>pid_t getpgid(pid_t pid);
功能:
    获取进程组号(PGID)
参数:
    pid:进程号
返回值:
    参数为 0 时返回当前进程组号,否则返回参数指定的进程的进程组号

进程创建

fork()

调用一次但返回两次,子进程中返回0值而父进程中返回子进程ID

#include <sys/types.h>
#include <unistd.h>pid_t fork(void);
功能:
    用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
参数:
    无
返回值:
    成功:子进程中返回 0,父进程中返回子进程 ID。pid_t,为整型。
    失败:返回-1。
    失败的两个主要原因是:
        1)当前的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN。
        2)系统内存不足,这时 errno 的值被设置为 ENOMEM。

fork后子进程和父进程信息区别

父子进程各自的地址空间是独立的
Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。

注意:fork之后父子进程共享文件,fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件文件偏移指针

使用 fork() 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间:包括进程上下文(进程执行活动全过程的静态描述)、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。
子进程所独有的只有它的进程号,计时器等(只有小量信息)

fork扩展

循环执行n次,产出2n-1即(1+2+4+……+2n-1)个子进程,打印2*(1+2+4+……+2n)次

for(i=0;i<n;i++){  
   pid_t fpid=fork();  
   if(fpid==0)  
       printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
   else  
       printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
}  

进程退出

本进程退出

exit()exit() 函数功能和用法是一样的、exit()属于标准库函数,exit()属于系统调用函数

exit() & _exit()

#include <stdlib.h>
void exit(int status);#include <unistd.h>
void _exit(int status);
功能:
    结束调用此函数的进程。
参数:
    status:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。
返回值:
    无

在这里插入图片描述

等待子进程退出

在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。

父进程可以通过调用waitwaitpid得到它的退出状态同时彻底清除掉这个进程。

wait()waitpid() 函数的功能一样,区别wait() 函数会阻塞,waitpid() 可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。
注意:一次wait或waitpid调用只能清理一个子进程

wait()
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);
功能:
    等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。
参数:
    status : 进程退出时的状态信息。
返回值:
    成功:已经结束子进程的进程号
    失败: -1

调用 wait() 函数的进程会挂起(阻塞),直到它的一个子进程退出或收到一个不能被忽视的信号时才被唤醒(相当于继续往下执行)。若调用进程没有子进程或子进程已经结束,该函数立即返回;
status退出信息在一个 int 中包含了多个字段,直接使用无意义的,要用宏函数取出其中的每个字段。

解析status字段宏函数
  1. WIFEXITED(status)
    为非0 → 进程正常结束
    WEXITSTATUS(status)
    如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
  2. WIFSIGNALED(status)
    为非0 → 进程异常终止
    WTERMSIG(status)
    如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
  3. WIFSTOPPED(status)
    为非0 → 进程处于暂停状态
    WSTOPSIG(status)
    如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
    WIFCONTINUED(status)
    为真 → 进程暂停后已经继续运行
waitpid()
#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);
功能:
    等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。
​
参数:
    pid : 参数 pid 的值有以下几种类型:
      pid > 0  等待进程 ID 等于 pid 的子进程。
      pid = 0  等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它。
      pid = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。
      pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。
​
    status : 进程退出时的状态信息。和 wait() 用法一样。
​
    options : options 提供了一些额外的选项来控制 waitpid()0:同 wait(),阻塞父进程,等待子进程退出。
            WNOHANG:没有任何已经结束的子进程,则立即返回。
            WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)
                 
返回值:
    waitpid() 的返回值比 wait() 稍微复杂一些,一共有 3 种情况:
        1) 当正常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号;
        2) 如果设置了选项 WNOHANG,而调用中 waitpid() 发现没有已退出的子进程可等待,则返回 03) 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在,如:当 pid 所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid() 就会出错返回,这时 errno 被设置为 ECHILD;

进程替换

exec 函数族

#include <unistd.h>
extern char **environ;int execl(const char *path, const char *arg, .../* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);int execve(const char *filename, char *const argv[], char *const envp[]);

execve() 才是系统调用,其它是在此基础上包装的库函数

exec 函数族的作用是在调用进程内部执行一个可执行文件。该进程完全由新程序替换,不创建新进程,仅替换当前进程的正文、数据、堆和栈段(进程替换)

exec 函数族中的函数执行成功后不会返回,所以exec 函数族下面的代码执行不到。只有调用失败了,才会返回 -1,失败后从原程序的调用点接着往下执行。

exec函数族使用说明

在这里插入图片描述

l(list)参数地址列表,以空指针结尾
v(vector)存有各参数地址的指针数组的地址
p(path)按 PATH 环境变量指定的目录搜索可执行文件
e(environment)存有环境变量字符串地址的指针数组的地址

僵尸进程

进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。无人回收其进程号就会一直被占用,但系统能使用的进程号有限。应避免。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值