进程的定义

  • 从用户的角度来看,进程是一个程序的动态执行过程。是一个把静态程序文件加载进内存、分配资源、执行、调度和消亡的过程。
  • 从操作系统的角度来看,进程是资源分配的基本单位进程需要占用CPU资源以执行程序指令;而除了需要占用CPU资源以外,进程还需要占据存储资源来保存状态。进程需要保存的内容包括数据段、代码段、堆、栈以及其他内存空间,进程也需要占用资源管理打开的文件、挂起的信号、内核内部数据、处理器状态、存在内存映射的内存空间以及执行线程,而执行线程的信息则包含程序计数器、栈和寄存器状态。

常用函数

  1. fork()

    • 创建子进程

      pid_t pid = fork();
      if(pid == 0){//子进程
      }else{//父进程
      }
      简便方法:
      if(fork()==0){ 
      }else{
      }
      
    • 父子进程对同一个文件不会共享读写位置。

  1. exec

    • exec是一系列的系统调用。它们通常适用于在fork之后,用于在当前进程的上下文中加载并运行一个新的程序。

    • 当进程执行到exec系统调用的时候,它会将传入的指令来取代进程本身的代码段、 数据段、栈和堆,然后将PC指针重置为新的代码段的入口。 但进程的 PID 保持不变。

    • execl()

      定义:
      int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
           
      用法:
      execl("/home/snow/code/test/11_process/05_test", "./05_test", "1", "2",(char *)0);
      // path参数表示可执行文件路径
      // 函数名当中的l表示列表list的含义,它要求传入可变数量的参数,并且每个参数对应一个命令行参数,最后以0结尾
      // 如果执行成功,它不会返回到原来的程序,因为原程序的执行内容已被新程序替换。如果有返回值意味着出现了错误,将返回-1
      
    • execv()

      定义:
      int execv(const char *path, char *const argv[]);
           
      用法:
      execv("/home/snow/code/test/11_process/05_test", args);
      // 函数名当中的v表示向量vector的含义,它要求传入一个指针数组,数组中的每个元素指向同一个字符串的不同位置
      
    • 函数原型

      int execl(const char *path, const char *arg, ...);
      int execlp(const char *file, const char *arg, ...);
      int execle(const char *path, const char *arg,..., 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[]);
                           
      //path:可执行文件的路径名字
      //arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
      //file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
      //返回值:exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
      //p:使用文件名,并从PATH环境进行寻找可执行文件
      //v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
      //e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量
      

      ps:实际上,system函数以及从bash或者是其他shell启动进程的本质就是fork+exec

  1. exit

    • _Exit_exitexit 都是用于立即终止进程的函数,但它们在处理进程终止时的行为上有所不同:

      • 当调用 _exit_Exit的时候,进程会直接终止返回内核,
      • exit函数则会首先执行终止处理程序,然后清理标准IO (就是把所有打开的流执行一次fclose ),最后再终止进程回到内核。
    • 在进程阶段,进程总共有5种终止方式,其中3种是正常终止,还有2种是异常终止:

      终止方式 终止情况
      main函数中调用return 正常
      调用exit函数 正常
      调用_Exit函数或者_exit函数 正常
      调用abort函数 异常
      接收到引起进程终止的信号 异常

进程控制

  1. 孤儿进程(没啥危害)
    • 子进程还存在,父进程就退出了,此时子进程自动被PID为1的进程收养。
  2. 僵尸进程(有危害)
    • 子进程退出但是父进程未调用wait或waitpid函数进行清理,则仍占用一个进程表条目,若产生大量僵尸进程,进程表耗尽,则无法创建新进程。
  3. wait()

    • 随机地等待一个已经退出的子进程,返回该子进程的PID,并进行清理回收子进程资源工作。
    • wait是一个阻塞函数, wait会一直阻塞直到等到一个结束的子进程, 解除阻塞.(前提是有子进程,没有子进程的时候, 直接返回-1)
    • wait(int *wstatus);参数用于存储进程的退出状态,若不关心可以为NULL
  4. waitpid()

    • 指名道姓的wait()

    • pid_t waitpid( pid_t pid, // 指定等待的PID的子进程 int *wstatus, // 存储进程的退出状态 int options // 修改 waitpid 的行为的选项 );

    • pid参数:

      • pid参数可以控制支持更多种模式的等待方式。
      PID数值 效果
      < -1 等待进程PID和pid绝对值的进程
      == -1 等待任一个子进程, 等价于wait()
      == 0 等待同一进程组的任意子进程
      > 0 等待指定PID的进程(此为正常情况,即直接写pid)

守护进程

  1. 用户组与权限

    • 进程在运行过程中必须具有用户身份,以便于内核进行进程的权限控制。在默认情况下,程序进程拥有启动用户的身份。
      • 假设当前登录用户为A,他运行了任意一个程序, 无论是不是他创建的,则程序在运行过程中就具有A的身份,该进程的用户ID组ID分别为AA所属的组,其ID和组ID就被称为进程的真实用户ID真实组ID
      • 真实用户ID和真实组ID可以通过函数getuid()和getgid()获得。
    • 进程除了真实用户ID真实组ID以外, 进程还具有有效用户ID有效组ID的概念。

      • 当系统内核对进程的访问权限检查时,它检查的是进程的有效用户ID和有效组ID,而不是真实用户ID和真实组ID
      • 默认情况下,用户的有效用户ID和真实用户ID是相同的,有效组ID和真实组ID是相同的。
      • 有效用户ID和 有效组ID通过函数geteuid()和getegid()获得。

    ps: 拓展

    // 更改文件或目录的所有者和所属组
    sudo chown root:root 文件
    // 增加用户s权限
    sudo chmod u+s 文件
    
    1. 守护进程
      • 一直运行在后台,如日志记录,系统监控
      • 守护进程的创建流程
        • 父进程创建子进程,然后让父进程终止。
        • 在子进程当中创建新会话。
        • 修改当前工作目录为根目录。
        • 重设文件权限掩码为0,避免创建文件的权限受限。
        • 关闭不需要的文件描述符,比如0、1、2。

更新时间: