Unix/Linux操作系统-进程管理

一、基本概念

  1. 程序:存储在磁盘上的文件,包含可执行指令和数据的静态实体。
  2. 进程:运行中的程序。一个程序可以执行多次,加载出多个进程。活动状态下的计算机程序。
  3. 进程的分类:交互进程、批处理进程、守护进程
  4. 交互进程:有输入输出,用户可以根据自己的情况输入数据,得到想要的结果(一般情况下的进程)
  5. 批处理进程:由脚本加载执行的程序(Linux下的shell,Windows下的bat)
  6. 守护进程:总是活跃的,后台执行,开机时加载执行或root用户手动加载执行
  7. 查看进程
    – 简单方式:ps:显示当前用户有终端控制权的进程信息
    – 列表形式:ps aux:以列表形式显示详细信息
    – a:所有用户的终端控制进程
    – x:所有用户的无终端控制进程
    – u:详细的显示方式
  8. 进程的详细信息列表
    USER:进程的属主
    PID:进程号(ID)
    %CPU:CPU使用率
    %MEM:内存使用率
    VSZ :占用虚拟内存大小
    RSS:占用物理内存大小
    TTY:有终端控制的显示终端的次设备号,无终端控制的显示”?“
    STAT:进程的状态
    O:就绪态-等待被系统调度
    R:运行态-Linux系统下没有O,就绪态也用R表示
    S:休眠态-可以被系统中断,信号唤醒
    T:暂停态-是被SIGSTOP信号暂停的,当收到SIGCONT信号时才能再转入运行态
    Z:僵尸态-已经结束停止运行,但父进程还没有回收
    <:高优先级进程
    N:低优先级进程
    l:多线程化的进程
    +:在前台进程组中的进程
    s:会话首进程
    START TIME:进程的开始时间
    COMMAND:进程的可执行文件名
  9. 一个进程A可以创建出另一个进程B,创建者叫父进程,被创建进程叫子进程,父进程启动子进程后,在操作系统的调用下父进程同时执行(同步)。
  10. 如果子进程先于父进程结束,会向父进程发送SIGCHLD信号,父进程收到信号后,就应该去回收子进程的相关资源,但在默认情况下,父进程忽略该信号。
  11. 当子进程结束后,父进程没有回收子进程的资源,那么子进程就变成了僵尸进程。
  12. 如果父进程先于子进程结束,子进程就变成了孤儿进程,同时被孤儿院收养(init),然后就变成了init的子进程。

二、进程标识符

  1. 操作系统会为每个进程分配一个唯一的标识符,采用无符号整数表示,即进程ID。
  2. 进程ID在任何时候都是唯一的,但是可以重用,当一进程结束,新创建的进程才可以使用它的进程ID(延时重用)。
  3. pid_t getpid(void);功能:获取进程ID
  4. pid_t getppid(void);功能:获取父进程ID
  5. uid_t getuid(void);功能:获取当前进程的用户ID
  6. gid_t getgid(void);功能:获取当前进程的组ID
  7. pid_t getpgid(pid_t pid);功能:获取pid进程的进程组ID
  8. int setpgid(pid_t pid, pid_t pgid)功能:设置进程pid的进程组ID

三、fork

#include <unistd.h>
  1. pid_t fork(void);
  2. 功能:创建一个新进程
  3. 返回值:一次调用两次返回,失败返回-1(当进程数走出系统的限制进程创建就会失败)
  4. 两次返回分别是进程ID和0,父进程拿到子进程ID,子进程返回0,借此可以分出父子进程,编写不同的处理分支。一般解决一些多任务,并发执行
  5. 通过fork创建的子进程就是父进程的副本(拷贝),子进程会获取父进程的数据段,bss段,堆,栈,IO流(共享文件指针和文件描述符),缓冲区的拷贝,与父进程共享代码段
  6. 子进程会继承父进程的信号处理方式
  7. fork函数调用后,父子进程各自执行,谁先返回不一定,但是可以使用一些手法来规定谁先执行。

练习1. 实现一程序来验证:子进程会获取父进程的数据段,bss段,堆,栈,IO流
练习2 为一个父进程创建5个子进程,一个6个进程

  1. 僵尸进程与孤儿进程的实现

四、进程的正常退出

  • 一、从main函数中return
  • 二、调用标准库中的exit函数
    • void exit(int status);
    • 功能:调用者立即结束该进程
    • status:退出状态码,可以再父进程中获取到,子进程留给父进程的遗言
    • 退出前做的事情:
      • 1、先调用实现注册的函数(atexit/on_exit)
        • int on_exit(void (*function)(int , void *), void *arg);
          • 功能:注册一个函数,当进程通过exit函数开始结束时调用
          • function:函数指针,无返回值,参数1为exit函数的参数,参数二,为on_exit函数的第二个参数.
          • arg:当function函数被调用是传递给它第二参数。
        • int atexit(void (*function)(void));
          • 功能:注册一个函数,当进程通过exit函数结束时调用。
          • function:函数指针,无返回值无参数。
      • 2、fflush:冲刷所有处在未关闭状态的标准IO流,删除所有临时文件
      • 3、返回一整数给操作系统,一般为0(EXIT_SUCCESS)或1(EXIT_FAILURE)
      • 4、该函数不会返回并且它的功能借助了_exit /_Exit
  • 三、调用_exit/_Exit函数退出
    • 注意
      • 这两个函数的功能是一样的
    • #include <unistd.h>
      • void _exit(int status);
    • #include <stdlib.h>
      • void _Exit(int status);//调用系统的_exit
    • 功能
      • 调用的进程会结束,没有返回值
    • status
      • 会被父进程获取到(低八位,一个字节)
      • 进程结束前会关闭所有处于打开状态的文件描述符。
      • 把所有的子进程托付给init(孤儿院)
      • 向它的父进程发送SIGGHLD信号。
      • 注意:exit也会进行以上操作。(底层调用了这两个函数)
  • 四、进程的最后一个线程执行了结束语句
  • 五、进程的最后一个线程调用了pthread_exit函数

五、进程的异常退出

  1. 调用了abort函数,该函数会产生SIGABRT信号
  2. 进程接收到一些信号(无法捕获处理、或无法捕获处理)
  3. 进程的最后一个线程收到取消请求,并做出相应,相当于线程收到了结束信号。

六、wait / waitpid

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
  • 功能:等待所有子进程结束,并获取到最终的状态码,只要有一个进程结束就立即返回。
  • 应该是父进程收到子进程发送来的SIGCHLD信号时,调用wait函数回收子进程的资源并获取结束状态。
  • 如果所有子进程都在运行,则wait阻塞。
  • 如果已有僵尸进程,wait也会立即返回,回收资源获取结束状态码。
  • 如果没有子进程,则返回失败-1。
pid_t waitpid(pid_t pid, int *status, int options);
  • 功能:等待指定的进程结束,并获取到最终的状态码。
  • pid
    • 负1:等待任一子进程结束,此时与wait等价
    • 大于0:等待进程号为pid的进程结束,此时只等待一个进程结束
    • 等于0 :等待同组的任一子进程结束, 此时等待的是整个进程组
    • 小于负1:等待的是进程组id是pid的绝对值中的任一子进程结束,此时等待的是整个进程组。
  • options:
    • WNOHANG 非阻塞模式,如果没有子进程结束则立即退出
    • WUNTRACED 如果子进程处理暂停,则返回它的状态
    • WCONTINUED 如果子进程从暂停转为继续,则返回它的状态

注意

  1. wait函数只能孤独的等待子进程结束,而waitpid可以有更多的选择。
  2. waitpid不光可以等待子进程,也可以等待同组进程。
  3. waitpid可以阻塞也可以不阻塞。
  4. 也可以监控子进程的暂停或继续状态。

七、vfork

#include <unistd.h>
#include <sys/types.h>
pid_t vfork(void);

功能:与fork的功能基本一致
区别:通过vfork创建的进程不复制父进程的地址空间(数据段、bss段、堆、栈、IO流、缓冲区的拷贝),必须通过exec系列函数加载自己的可执行程序。
注意:当执行vfork时,子进程先返回(先执行),此时它占用了父进程的地址空间,当子进程成功创建后(通过exec加载可执行程序),父进程才返回(后执行)。

八、exec

加载子进程的可执行文件。
#include <unistd.h>
  1. int execl(const char *path, const char *arg, ...);
    path:可执行文件的路径
    arg:第一个main函数的参数,最后一次必须以NULL结尾。
  2. int execlp(const char *file, const char *arg,...);
    file:可执行文件的名字,会从PATH环境变量的路径中查找可执行文件并执行。
    arg:第一个main函数的参数,最后一次必须以NULL结尾。
  3. int execle(const char *path, const char *arg,..., char * const envp[]);
    path:可执行文件的路径
    arg:第一个main函数的参数,最后一次必须以NULL结尾。
    envp:父进程的环境变量表,传递给子进程。
  4. int execv(const char *path, char *const argv[]);
    path:可执行文件的路径
    argv:命令行参数
  5. int execvp(const char *file, char *const argv[]);
    file:可执行文件的名字,会从PATH环境变量的路径中查找可执行文件并执行。
    argv:命令行参数
  6. int execvpe(const char *file, char *const argv[],char *const envp[]);
    file:可执行文件的名字,会从PATH环境变量的路径中查找可执行文件并执行。
    argv:命令行参数
    envp:父进程的环境变量表,传递给子进程。

九、system

#include <stdlib.h>
  • int system(const char *command);
  • 功能
    • 执行系统命令的,也可以加载可执行程序。
      相当于创建一个子进程,但子进程不结束,该函数不返回,父子进程不会同时执行。
      该函数的实现应该调用了:fork、exec等函数。

十、进程组

  • 是由一个或多个进程的集合,每个进程除有一个进程ID还有一个进程组ID,进程组中的进程归属同一个作业控制(负责完成同一个任务)。

  • 同一进程组的进程,会统一接收到终端的信号,由fork创建的子进程,默认就加入了父进程的进程组。

  • 每个进程组都有一个组长,组长的进程ID就是组ID

  • pid_t getpgid(pid_t pid); //获取pid进程的进程组ID

  • int setpgid(pid_t pid, pid_t pgid); //设置进程pid进程的进程组ID,就相当于加入pgid进程组。pgid就是它的组长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值