进程重要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) | 如果子进程的被信号暂停,且 option 中 WUNTRACED 已经被设置时,该宏为真 |
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