Linux进程函数

发布时间 2023-10-28 19:48:16作者: imXuan

1.进程相关知识

PCB进程控制块包含的信息

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

具体更多操作系统相关的知识可以看这里的随笔  <操作系统 - 随笔分类 - imXuan - 博客园 (cnblogs.com)>

2.进程创建

2.1 fork

  功能:用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。

  fork创建子进程,两个进程逻辑上虽然是完全用虚拟内存进行隔离的,但实际上linux引入了读时共享,写时复制的原则,共同读取的数据不需要复制,需要写入的时候再复制,节省空间,具体可以参考操作系统随笔中的内容

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

2.2 getpid

  功能:获取本进程号(PID)

#include <sys/types.h>
#include <unistd.h>
​
pid_t getpid(void);
// 返回值:本进程号

2.3 getppid

  功能:获取调用此函数的进程的父进程号(PPID)

#include <sys/types.h>
#include <unistd.h>
​
pid_t getppid(void);
// 返回值:调用此函数的进程的父进程号(PPID)

2.4 getpgid

  功能:获取进程组号(PGID)

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

2.5 exec 函数族

  将当前进程的代码段、数据段等替换成所需要加载程序的代码段、数据段,从新的代码段的第一条指令开始执行,但进程ID不变

  exec函数族函数一旦调用成功,不会返回值,只有失败才返回 -1 或 errno

2.5.1 execlp

int execlp(const char* file, const char* arg, ... /* (char*) NULL */);
/*
参数
   file: 加载程序的名字,需要配合环境变量 PATH 使用
   arg0: 可执行文件名
   arg1: 参数
    ...
   argn: NULL (哨兵)  
*/

  示例

execlp("ls", "ls", "-l", "-F", "-a", NULL); 

  补充

int main( int argc, char* argv[])
{ 
     //函数体内使用了argc或argv
     ……
     return 0;
}
//    argv[0]指向程序运行的全路径名 
//    argv[1]指向在命令行中执行程序名后的第一个字符串 
//    argv[2]指向执行程序名后的第二个字符串 

2.5.2 execl

int execlp(const char* file, const char* arg, ... /* (char*) NULL */);
/*
参数
   file: 加载程序的绝对路径的程序名字
   arg0: 可执行文件名
   arg1: 参数
    ...
   argn: NULL (哨兵)  
*/

  示例

execl("./bin/ls", "ls", "-l", "-F", "-a", NULL); 

3.进程回收

  • 父进程有义务在子进程结束时,回收该子进程,隔备进程无回收关系
  • 进程终止:
    • 关闭所有文件描述符
    • 释放用户空间分配的内存
    • 进程的 pcb 残留在内核。保存进程结束的状态(正常:退出值。异常:终止其运行的信号编号)

3.1 孤儿进程

  父进程先于子进程终止,子进程沦为“孤儿进程”,会被 init 进程领养

  ps ajx 指令可以查看进程信息

3.2 僵尸进程(zombie)

  子进程终止,父进程未终止,但父进程尚未对子进程进行回收

  结束进程指令:kill -9 进程id。只能结束活跃进程,僵尸进程无效,僵尸进程已经结束,只是父进程没有把他干掉,PCB残留在内核中

3.3 wait 回收

  • 功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收该子进程的资源。
  • 作用
    • <阻塞>等待子进程退出(终止)
    • 回收子进程残留在内核的pcb
    • 获取子进程的退出状态(正常、异常),传出参数:status
#include <sys/types.h>
#include <sys/wait.h>
​
pid_t wait(int *status);
/*
    参数:
        status : 进程退出时的状态信息。
    返回值:
        成功:已经结束子进程的进程号
        失败: -1
*/

  示例,通过宏可以获取退出码或者信号编号,也可以传入NULL,不需要保存任何信息,只是把子进程回收

int main(int argc, char *argv[])
{
    int status = 9;
    pid_t wpid = 0;
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork err"); exit(1);
    }
    else if(pid == 0)
    {
        printf("I'm child pid = %d\n", getpid());
        sleep(3);
        exit(66);
    }
    else
    {
        wpid = wait(&status);    // 保存子进程退出的状态
        if(wpid == -1)
        {
            perror("wait err"); exit(1);
        } 
        if(WIFEXITED(status))    // 宏函数为真,说明子进程正常退出
        {    // 获取退出码
            printf("I'm parent, pid = %d child, exit code = %d\n", wpid, WEXITSTATUS(status));
        }
        else if(WIFSIGNALED(status))    // 宏函数为真, 说明子进程被信号终止
        {    // 获取信号编码
            printf("I'm parent, pid = %d child, killed by %d signal\n", wpid, WTERMSIG(status));
        }
    }
    return 0;
}

3.4 waitpid 回收

pid_t waitpid(pid_t pid, int* status, int options);
/*
     pid > 0  等待进程为 pid 的子进程。
     pid = 0  等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它。
     pid = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。
     pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。

     status: 传出回收子进程状态
     options: WNOHANG -- 指定回收方式为 “非阻塞”;  0 为阻塞方式
     
     成功返回回收进程的 pid , 失败返回 -1 , 子进程未结束则返回 0 (用非阻塞回收)
*/

  **注意:一次 wait 、 waitpid  调用只能回收一个子进程,想回收 N 个子进程需要将函数放于循环中  

4.进程间通信