Linux进程管理

一、基本概念

1、查看进程
简单形式:
	ps命令以简略方式显示当前用户所有控制终端的进程信息。
复杂形式:
	ps axuw 命令以列表形式显示进程详细信息
		a - 所有用户所有控制终端的进程
		x - 包括无控制终端的进程
		u - 以详尽方式显示
		w - 以更大列宽显示

进程信息列表

  • USER(UID): 进程属主。
  • PID: 进程ID。
  • %CPU©: CPU使用率。
  • %MEM: 内存使用率。
  • VSZ: 占用虚拟内存大小(KB)。
  • RSS: 占用物理内存大小(KB)。
  • TTY: 终端次设备号,“?”表示无控制终端,如后台进程。
  • STAT(S): 进程状态。可取如下值:
    • O - 就绪,等待被调度。
    • R - 运行,Linux下没有O状态,就绪状态也用R表示。
    • S - 可唤醒睡眠。系统中断,获得资源,收到信号,都可被唤醒,转入运行状态。
    • D - 不可唤醒睡眠。只能被wake_up系统调用唤醒。(没资源?)
    • T - 暂停。收到SIGSTOP信号转入暂停状态, 收到SIGCONT信号转入运行状态。
    • W - 等待内存分页(2.6内核以后被废弃)。
    • X - 死亡。不可见。
    • Z - 僵尸。已停止运行,但其父进程尚未回收相关资源。
    • < - 高优先级。
    • N - 低优先级。
    • L - 有被锁到内存中的分页。实时进程和定制IO。
    • s - 会话首进程。(父进程?操作系统调用?)
    • l - 多线程化的进程。
    • /+ - 在前台进程组中。
  • START/STIME: 进程开始时间。
  • TIME: 进程运行时间。
  • COMMAND/CMD: 进程指令。
  • F: 进程标志,可由下列值取和:
    • 1 - 通过fork产生但是没有exec。
    • 4 - 拥有超级用户特权。
  • PPID: 父进程ID。
  • NI: 进程nice值,-20到19,可通过系统调用或命令修改。
  • PRI: 进程优先级。
    静态优先级 = 80 + nice,60到99,值越小优先级越高。
    内核在静态优先级的基础上,根据进程的交互性计算得到实际(动态)优先级,以体现对IO消耗型进程的奖励,和对处理器消耗型进程的惩罚。
  • ADDR: 内核进程的内存地址。普通进程显示“-”。
  • SZ: 占用虚拟内存页数。
  • WCHAN: 进程正在等待的内核函数或事件。
  • PSR: 进程被绑定到哪个处理器。
2、父进程与子进程
  • 一个进程可以创建另一个进程,创建者叫做父进程,被创建都叫子进程。

  • 父进程启动子进程后,子进程在操作系统的调度下与其父进程同时运行。

3、孤儿进程与僵尸进程
  • 正常情况下:子进程先于父进程结束,子进程向父进程发送SIGCHLD(17)信号,父进程回收子进程的相关资源。
  • 父进程先于子进程结束,子进程成为孤儿进程,同时被孤儿院进程收养,即成为孤儿院进程的子进程,早期的孤儿院是init,现在的孤儿院是upstart。
  • 子进程先于父进程结束,但父进程没有回收子进程的相关资源, 。
4、进程标识符
  • 每个进程都有一个以非负整数表示的唯一标识,即进程ID,简称PID。

  • 进程ID在任何时刻都是唯一的,但可以重用,当一个进程结束时,其进程ID就可以被分配其它新创建的进程。

  • 延迟重用,当进程结束后,它的进程ID并不会被操作系统立即重新分配出去,而隔一段时间再重新分配出去。

    #include <stdio.h> 
    #include <unistd.h>
    
    pid_t getpid(void);
    功能:获取当进程的ID
    pid_t getppid(void);
    功能:获取当前进程的父进程ID
    

二、进程创建与管理

1、fork创建进程
#include <unistd.h>
pid_t fork(void);
功能:创建一个子进程
返回值:失败返回-1,成功返回两次
    父进程:返回子进程的pid,这是唯一一次获得子进程ID的机会
    子进程:返回0
    
注意:总进程数或实际用户ID所拥有的进程数,超过系统限制,该函数将失败。

通过fork创建的子进程与父进程的关系:

函数调用后父子进程各自继续运行,其先后顺序不确定,并且子进程会复制父进程的data、bss、heap、stack、IO缓冲区,共享父进程的代码段、文件表(子进程有权访问父进程所有的文件描述符,只要把文件描述符告诉他),继承父进程的信号处理方式

2、vfork和exec系列函数创建进程
pid_t vfork(void);
功能:创建子进程,返回值与fork相同
vfork函数的特点:
  • 当调用vfork函数后,父进程会进入阻塞状态,子进程先返回。
  • 子进程先临时使用父进程的相关资源,然后继续调用exec系列函数加载一个可执行文件,启动另一个进程替换自己。
  • 等子进程调用完exec系列函数后,子进程才真正创建完毕后,父进行才会解除阻塞返回。
  • 如果子进程不调用exec系列函数,会有两种情况:
    • 情况1:子进程一直无结果,则父进程就一直陷入阻塞状态,无法返回。
    • 情况1:子进程直接结束并释放相关资源,但此时子进程使用的是父进程的相关资源,然后父进程返回,此时会就产生段错误,因为它的相关资源已经被子进程释放了。
  • vfork不能单独使用创建子进程,必须与exec系列函数配合使用。
vfork与fork的区别:
  • vfork调用之后,子进程先返回,而fork调用后,父子进程不一定谁先返回。

  • vfork不会复制父进程的相关资源,而是加载其它的可执行文件进行替换。

exec系列函数:
这一系列函数的功能就是配合vfork创建子进程。
int execl(const char *path, const char *arg, ...);
path:程序的路径
arg:命令行参数
 	// 例如执行系统ls:  "/bin/ls","ls","-l",NULL       NULL结尾!
int execlp(const char *file, const char *arg, ...);
file:只需要程序的名字,系统会去PATH环境变量的路径中来查找这个程序。
arg:命令行参数

int execle(const char *path, const char *arg,..., char * const envp[]);
envp:默认子进程继承父进程环境变量表(操作系统提供的),使用该函数创建子进程时,父进程可自定义一张环境变量表传递给子进程使用

int execv(const char *path, char *const argv[]);
argv:命令参数以指针数组的形式提供,以NULL作为结尾

int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

三、进程的正常结束

1、调用标准C的exit函数结束进程:
#include <stdlib.h>
void exit (int status);

int main (...) 
{
    ...
    return x;
}

等价于:
int main (...) 
{
    ...
    exit (x);
}
  • 该函数一旦调用就不会返回,其父进程调用wait/waitpid函数可获得status的低8位数据。

  • 进程退出之前先调用所有事先通过atexit/on_exit函数注册的函数,冲刷并关闭所有仍处于打开状态的标准I/O流,删除所有通过tmpfile函数创建的文件。

  • 可用EXIT_SUCCESS(0)/EXIT_FAILURE(1)常量宏作参数,表示进程是否正常结束。

  • 该函数的底层调用了_exit/_Exit函数。

    main函数中显式地使用exit(0)以后,exit(main());就不会再调用了。所以,一个程序真正的退出,在C语言层面上,实际是exit()函数,而main函数的作用,则是给exit()提供返回值。(main函数的返回值实际上是有意义的,是用于exit()的参数)

#include <stdlib.h>

int atexit (void (*function) (void));
function - 函数指针,指向进程退出前需要被调用的函数,该函数既没有返回值也没有参数。

int on_exit (void (*function) (int, void*), void* arg);
function - 函数指针,指向进程退出前需要被调用的函数,该函数没有返回值但有两个参数:
           第一参数来自exit函数的status参数,第二个参数来自on_exit函数的arg参数。
arg      - 任意指针,将作为第二个参数被传递给function所指向的函数。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void atexit_fp(void)
{
    printf("我要结束了...!\n");
}

void on_exit_fp(int status,void* ptr)
{
    printf("我要结束了...,status=%d,%s\n",status,(char*)ptr);
}

int main(int argc,const char* argv[])
{
    // 谁先登记的谁后执行 栈嘛
    on_exit(on_exit_fp,"test");
    atexit(atexit_fp);

    printf("我是进程%u\n",getpid());
    return 0; // 等价于exit(0);
}
/*
我是进程4166
我要结束了...!
我要结束了...,status=1,test
*/
2、调用_exit/_Exit函数结束进程:
#include <unistd.h>
void _exit (int status);

该函数有一个完全等价的标准C版本:
#include <stdlib.h>
void _Exit (int status);
  • 该函数一旦调用就不会返回,其父进程调用wait/waitpid函数可获得status的低8位数据。

  • 进程退出之前,先关闭所有仍处于打开状态的文件描述符,将其所有子进程托付给孤儿院收养(init/upstart),向父进程递送SIGCHILD信号。

3、其它正常结束进程的方法:
  • 进程的最后一个线程执行完毕。

  • 进程的最后一个线程调用pthread_exit函数。

四、进程的异常终止

  • 进程收到某些信号,比如kill(他杀)。
  • 进程自己调用abort函数,产生了SIGABRT信号,(自杀)。
  • 进程的最后一个线程收到"取消"操作,并作出响应。
  • 进程如果是异常结束的,on_exit/atexit登记的函数不会执行,缓冲区也不会冲刷,但依然会给父进程发送SIGCHLD信号并关闭所有打开状态的文件描述符。

五、子进程的资源回收

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

pid_t wait(int *status);
功能:以阻塞状态等待子进程结束,并回收它的相关资源,只要有一个子进程结束它就返回,如果没有子进程就立即返回(-1)。
status:获取子进程的退出状态码,是输出型的参数。
返回值:成功返回子进程的PID,失败返回-1。
    
子进程的终止状态码status可借助sys/wait.h中定义的参数宏解析查看:
	WIFEXITED()		- 子进程是否正常终止
    WEXITSTATUS()  解析出正确的返回值
	WIFSIGNALED() 	- 子进程是否异常终止,正常结束结果是0,异常结束结果是1
    
pid_t waitpid(pid_t pid, int *status, int options);
功能:以阻塞状态等待子进程结束,与wait不同的是它可以指定等待哪个进程。
pid:要等待的子进程PID
    <-1 等待abs(pid)进程组中的任意子进程结束
    =-1 等待任意子进程结束,此时功能与wait等价。
    =0  等待同组的任意进程结束。
    >0  此时它代表着一个指定的子进程。
status:获取进程的结束状态码。
    WIFSTOPPED(status) 		- 子进程是否处于暂停
	WIFCONTINUED(status)	- 子进程是否在暂停之后继续运行
options:
    0 表示忽略此参数,功能与wait一样。
    WNOHANG 如果没有子进程结束立即返回。
    WUNTRACED 如果有子进程暂停也立即返回
    WCONTINUED 如果有子进程从暂停状态切换为继续状态,立即返回。
返回值:成功返回子进程的PID,失败返回-1

六、进程组

进程组是一组相关联的进程,它们可以被当作一个单元来管理和控制。进程组通常由父进程创建,并共享一个进程组ID(PGID)。

要使用进程组,可以使用以下系统调用:

  • 创建进程组:使用setpgid(pid, pgid)函数可以将一个进程添加到指定的进程组。其中pid是要加入进程组的进程ID,pgid是进程组ID。如果pgid为0,表示将pid所属的进程加入到其父进程的进程组中,如果该父进程不存在,则将创建一个新的进程组。

  • 获取进程组ID:使用getpgid(pid)函数可以获取一个进程的进程组ID。其中pid是要获取进程组ID的进程ID。

  • 获取当前进程组ID:使用getpgrp()函数可以获取当前进程所属的进程组ID。

  • 在进程组中发送信号:使用killpg(pgid, signal)函数可以向指定的进程组发送信号。其中pgid是进程组ID,signal是要发送的信号。

通过这些函数,可以实现对进程组的创建、管理和控制。例如,可以将一组相关的子进程放在同一个进程组中,并通过发送信号给进程组来管理这些子进程的执行。

组中,如果该父进程不存在,则将创建一个新的进程组。

  • 获取进程组ID:使用getpgid(pid)函数可以获取一个进程的进程组ID。其中pid是要获取进程组ID的进程ID。

  • 获取当前进程组ID:使用getpgrp()函数可以获取当前进程所属的进程组ID。

  • 在进程组中发送信号:使用killpg(pgid, signal)函数可以向指定的进程组发送信号。其中pgid是进程组ID,signal是要发送的信号。

通过这些函数,可以实现对进程组的创建、管理和控制。例如,可以将一组相关的子进程放在同一个进程组中,并通过发送信号给进程组来管理这些子进程的执行。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值