跳到主要内容

进程重要API

1. 获取进程ID

pid_t getpid() 用于获取当前进程的进程ID。该函数原型为:

pid_t getpid(void);

该函数没有参数,它返回一个 pid_t 类型的值,表示当前进程的进程ID。

pid_t getppid() 用于获取当前进程的父进程的进程ID。该函数原型为:

pid_t getppid(void);

该函数没有参数,它返回一个 pid_t 类型的值,表示当前进程的父进程的进程ID。

2. 进程创建

2.1 API

pid_t fork(void) 是 Linux 操作系统中的一个函数,用于创建一个新的进程。该函数原型为:

pid_t fork(void);

该函数不需要任何参数,并返回一个 pid_t 类型的值。

当进程被成功创建时,该函数在原来的进程(父进程)中返回新进程的进程标识符(PID),在新进程中返回0。

如果进程创建失败,则该函数会返回一个负值。

注意
  • fork() 会使得进程本身被复制,因此被创建出来的子进程和父进程几乎是一模一样的。
  • 子进程会从 fork() 返回值后的下一条逻辑语句开始运行。
  • 父子进程的执行次序是随机的,无法判断谁先谁后,除非使用特殊机制同步它们。
  • 父子进程是相互独立、互不影响的。

2.2 父子进程共同点

  • 实际 UID 和 GID,以及有效 UID 和 GID。
  • 所有的环境变量
  • 进程组ID和会话ID
  • 当前工作路径,除非用 chdir( ) 进行修改。
  • 打开的文件
  • 整个内存空间,包括栈、堆、数据段、代码段、标准IO的缓冲区等等。
  • 信号响应函数

2.3 父子进程区别

  • 进程的PID
  • 挂起的信号
  • 记录锁

2.4 示例代码

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
pid_t pid;

// 使用 fork() 函数创建一个新的进程
pid = fork();

// 如果 pid 为 0,则表示当前进程为新创建的进程
if (pid == 0) {
printf("In child process\n");
}
// 如果 pid 大于 0,则表示当前进程为原来的进程(父进程)
else if (pid > 0) {
printf("In parent process\n");
}
// 如果 pid 小于 0,则表示创建进程失败
else {
printf("Failed to create new process\n");
}

return 0;
}

3. exec函数簇

3.1 第一组函数簇

Linux 的 exec 函数簇是一组函数,它们用于替换当前进程的程序段。exec 函数簇包括多个不同的函数,每个函数都有不同的参数和功能。

execl()\execv()\execle 函数用于在当前进程中执行一个新程序,并将该程序替换当前进程。这些函数原型为:

int execl(const char *path, const char *arg, ...);
int execv(const char *path, char * const argv[]);
int execle(const char *path, const char *arg, ..., char *const envp[]);

这些函数的参数:

  • path:一个字符串,表示要执行的程序的路径。
  • arg:一个字符串,表示执行程序时传递给它的第一个参数。
  • argv[]:表示传递给程序的参数数组。第一个参数必须是程序名,接下来的参数可以是任意数量的字符串。
  • ...:可变长度的参数列表,包含该程序的其余命令行参数。
  • char *const envp[]:一个指向环境变量的指针数组。

这些函数返回一个整型值,如果执行成功,则返回值为 0;如果执行失败,则返回一个非零值。

3.2 第二组函数簇

execlp\execvp\execvpe()函数也是用于在当前进程中执行一个新程序,并将该程序替换当前进程。这些函数原型为:

int execlp(const char *file, const char *arg,..);
int execvp(const char *file, char *const argv[ ]);
int execvpe( const char *file, char *const argv[ ],char *const envp[ l);

这些函数的参数:

  • file :指定要执行的程序文件的名称。
  • arg:一个字符串,表示执行程序时传递给它的第一个参数。
  • argv[]:表示传递给程序的参数数组。第一个参数必须是程序名,接下来的参数可以是任意数量的字符串。
  • ...:可变长度的参数列表,包含该程序的其余命令行参数。
  • char *const envp[]:一个指向环境变量的指针数组。

这些函数返回一个整型值,如果执行成功,则返回值为 0;如果执行失败,则返回一个非零值。

3.3 简单使用

  • 被加载的文件参数列表必须以自身名字为开始,以 NULL 为结尾。
  • 比如要加载执行当前目录下的 a.out 文件,需要一个参数 “abcd” 。或者执行 Linux 的 ls -l 命令。
excel("./a.out", "a.out", "abcd", NULL);

const char *argv[3] = {"./a.out", "abcd", NULL};
excev("./a.out", argv);

//执行bin下的ls命令,查找etc下的passwd文件,参数为ls
execlp("ls","ls","-l","/etc/passwd",(char * )0);

注意:exec 函数簇成功执行后,原有的程序代码都将被指定的文件或脚本覆盖,因此这些函数成功后,后面的代码是无法运行也无法返回的。

3.4 示例代码

示例:child.c 和 parent.c

// child.c
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
printf("child_pid: %d\n", getpid());

return 0;
}
disnox@MSI:/mnt/cde/exce$ gcc child.c -o child

// parent.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
pid_t pid = fork();
if(pid > 0){ //父进程
printf("parent_id: %d\n", getpid());
exit(0);
}
if(pid == 0){ // 子进程
printf("child_id: %d\n", getpid());
execl("./child", "child", NULL); // 执行 child 程序
printf("hello world\n"); // 这条代码会被覆盖掉
}

return 0;
}

4. 退出进程

4.1 退出函数

void exit()\void _exit() 是 Linux 操作系统中的一个函数,用于终止当前进程的执行。该函数原型为:

void exit(int status);
void _exit(int status);

这些函数需要一个参数

  • status:可以是任何整型值,也可以是标准错误码。

一旦调用了退出函数,当前进程就会立即终止执行,并向其父进程发送一个终止状态信号。

注意:在终止进程之前,exit() 函数会自动执行一些清理工作,如关闭打开的文件、释放分配的内存等。而_exit() 函数就不会执行这些清理工作。

4.2 注意

  • 正常退出进程指的是调用 exit( )/_exit( ),或者使用 return 关键字,但是 return 在子函数里面只是返回值,在 main 函数里面才是退出进程。
  • **pthread_exit( )**是线程退出。

4.3 程序退出处理函数注册

#include <stdlib.h>
int atexit(void (*function)(void));

示例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void f1(void)
{
printf("%s\n", __FUNCTION__);
}

void f2(void)
{
printf("%s\n", __FUNCTION__);
}

int main(int argc, char *argv[])
{
atexit(f1); // int atexit(void (*function)(void)) 注册一个要在正常流程终止时调用的函数
atexit(f2); // 注册函数是一个入栈的过程,先进后出
printf("hello"); //在缓冲区

//_exit(0); // 结束程序,但是不会清空缓冲区数据,也不可以处理注册函数
// exit(0); // 结束程序,并且可以处理注册函数,以及刷新缓冲区数据
// return 0;
}

5. 等待进程

5.1 API

pid_t wait()/pid_t waitpid() 是 Linux 操作系统中的一个函数,用于等待指定进程的终止。该函数原型为:

pid_t waitpid(pid_t pid, int *stat_loc, int options);

该函数需要三个参数:

  • pid:指定要等待的进程的进程标识符(PID)。
  • stat_loc:指向一个整型变量的指针,用于存储终止进程的状态信息。
  • options:指定等待进程的选项,状态宏。
    • WCONTINUED:报告任一从暂停态出来且从未报告过的子进程状态。
    • WNOHANG:非阻塞等待。
    • WUNTRACED:报告任一当前处于暂停态且从未报告过的子进程状态。

该函数会返回终止进程的 PID,如果没有进程终止,则返回零。

5.2 退出状态宏

处理子进程退出状态值的宏

含义
WIFEXITED(status)如果子进程正常退出,则该宏为真。
WEXITSTATUS(status)如果子进程正常退出,则该宏将获取子进程的退出值。
WIFSIGNALED(status)如果子进程被信号杀死,则该宏为真。
WTERMSIG(status)如果子进程被信号杀死,则该宏将获取导致他死亡的信号值。
WCOREDUMP(status)如果子进程被信号杀死且生成核心转储文件(core dump),则该宏为真。
WIFSTOPPED(status)如果子进程的被信号暂停,且 optionWUNTRACED 已经被设置时,该宏为真
WSTOPSIG(status)如果 WIFSTOPPED(status) 为真,则该宏将获取导致子进程暂停的信号值。
WIFCONTINUED(status)如果子进程被信号 SIGCONT 重新置为就绪态,该宏为真。
  • 父进程调用wait函数,会等待子进程退出
  • 父进程调用waitpid函数,会等待子进程状态变化 (waitpid函数更通用)

5.4 代码示例

以下示例代码,综合展示了如果正确使用 fork( )/exec() 函数簇,exit( )/_exit()wait( )/waitpid()

程序功能是:父进程产生一个子进程让他去程序child_elf,并且等待他的退出人可以用wait()阻塞等待,也可以用waitpid()非阻塞等待),子进程退出(可以正常退出,也可以异常退出)后,父进程获取子进程的退出状态后打印出来。详细代码如下:

  • child_elf.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
printf("[%d]: yep, I am the child\n", getpid());

#ifdef ABORT
abort(); // 自己给自己发送一个致命信号 SIGABRT 自杀
#else
exit(7); // 正常退出,且退出值为7
#endif
}
  • wait.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
pid_t pid = fork();

if(pid == -1){ // 进程创建失败
perror("fork");
}

if(pid == 0){ // 子进程,执行指定程序 child_elf
execl("./child_elf", "child_elf", NULL);
}

if(pid > 0){ // 父进程,使用 wait() 阻塞等待子进程的退出
int status;
wait(&status);

if(WIFEXITED(status)){ // 判断子进程是否正常退出
printf("child exit normally, exit value: %hhu\n", WEXITSTATUS(status));
}

if(WIFSIGNALED(status)){ // 判断子进程是否被信号杀死
printf("child killed by signal: %u\n", WTERMSIG(status));
}
}

return 0;
}
  • 运行
disnox@MSI:/mnt/e/Data/Code/wait$ gcc child_elf.c -o child_elf
disnox@MSI:/mnt/e/Data/Code/wait$ gcc wait.c -o wait
disnox@MSI:/mnt/e/Data/Code/wait$ ./wait
[340]: yep, I am the child
child exit normally, exit value: 7

disnox@MSI:/mnt/e/Data/Code/wait$ gcc child_elf.c -o child_elf -DABORT
disnox@MSI:/mnt/e/Data/Code/wait$ gcc wait.c -o wait
disnox@MSI:/mnt/e/Data/Code/wait$ ./wait
[352]: yep, I am the child
child killed by signal: 6