Days31 Linux 进程编程:waitpid、exec 族、环境变量 & 线程基础详解
前言
在 Linux 开发中,进程是资源分配最小单位,我们经常需要创建子进程、等待子进程退出、在进程中执行新程序,同时线程作为轻量级执行单元也是必学知识点。本文从waitpid开始,讲解进程回收、exec 函数族、环境变量,再过渡到 pthread 线程基础,包含函数原型、参数解析、使用场景、注意事项,适合嵌入式 / Linux C 开发入门学习。
阅读前提:已经掌握 fork 创建子进程、wait 函数基础用法。
一、waitpid 函数:回收指定子进程
1. 函数原型
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
2. 参数说明
-
pid:指定要回收的子进程 PID
表格
pid 取值 含义 >0回收 PID 等于该值的指定子进程 0回收调用进程组内任意子进程 -1回收任意子进程,等价于 wait () <‑1回收指定进程组内的任意子进程 - wstatus:保存子进程退出状态的地址,传入
NULL代表不关心退出状态。 可以使用宏解析 wstatus:
WIFEXITED(wstatus):判断子进程是否正常退出WEXITSTATUS(wstatus):获取子进程exit()返回值,仅正常退出时有效WIFSIGNALED(wstatus):判断子进程是否被信号杀死WTERMSIG(wstatus):获取杀死子进程的信号编号,仅被信号终止时有效
- options:回收选项,控制阻塞 / 非阻塞行为
0:阻塞回收。子进程没有退出,父进程就阻塞挂起,直到子进程结束。WNOHANG:非阻塞回收。如果子进程还没有结束,waitpid 立刻返回0,父进程不会卡住。
3. 返回值
- 大于 0:成功,返回被回收子进程的 PID
0:使用WNOHANG时,子进程还未退出-1:出错,比如没有子进程
4.wait 和 waitpid 对比
wait():只能阻塞等待任意一个子进程退出;waitpid():可以指定回收某个子进程,支持非阻塞,灵活性更高。
💡核心用途:解决僵尸进程。子进程执行完毕,父进程没有回收 PCB 资源就会产生僵尸进程,
waitpid可以选择阻塞或者轮询非阻塞回收子进程。
简单示例
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if(pid == 0){
//子进程
sleep(2);
printf("子进程%d即将退出\n",getpid());
exit(5);
}else if(pid >0){
int status;
//非阻塞轮询回收子进程
while(waitpid(pid,&status,WNOHANG) == 0)
{
printf("子进程还没结束,父进程做别的工作\n");
sleep(1);
}
if(WIFEXITED(status)){
printf("子进程正常退出,退出码:%d\n",WEXITSTATUS(status));
}
}
return 0;
}
二、exec 函数族:进程内部执行全新程序
1. 什么是 exec?
fork()创建子进程后,子进程复制父进程完整代码。如果想要子进程运行一个完全不一样的可执行程序,就使用exec 函数族。
重点:exec 调用成功之后,原来进程的代码段、数据段全部被新程序覆盖,exec 之后的代码不会执行;只有调用失败,才会返回‑1 继续向下运行。
2. 函数原型一览
extern char **environ;
//l:参数以可变参数列表传入,必须以NULL结尾
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
//v:参数传入字符串指针数组
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[]);
后缀含义记忆
表格
| 后缀 | 含义 |
|---|---|
l | list,参数列表,可变参数,末尾必须传(char*)NULL |
v | vector,字符串指针数组传参 |
p | path,会从环境变量PATH路径下搜索程序,不用写全路径 |
e | environment,可以自定义传给新程序的环境变量 |
注意:第一个参数是要执行程序名;第二个开始是命令行参数,第一个参数必须是程序本身名字,最后必须以 NULL 哨兵结尾。
示例:execlp 使用
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("准备调用exec执行ls\n");
//p会去PATH中找ls,不需要写/bin/ls
execlp("ls","ls","-l",NULL);
//只有exec失败才会走到这里
perror("execlp fail");
return 0;
}
开发常用套路:fork 创建子进程,子进程调用 exec 执行外部程序,父进程负责 waitpid 回收。
三、环境变量 setenv
环境变量是进程运行时保存配置信息的变量,子进程默认继承父进程全部环境变量。
函数原型
int setenv(const char *name, const char *value, int overwrite);
参数:
name:环境变量名字value:环境变量值overwrite:1:如果该环境变量已经存在,覆盖旧值0:已经存在则不做修改
返回值:成功返回 0,失败返回‑1。
extern char **environ;可以直接拿到当前进程全部环境变量表。
使用示例
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
setenv("MY_VAR","hello_csdn",1);
printf("MY_VAR = %s\n",getenv("MY_VAR"));
return 0;
}
四、线程基础 pthread
进程:操作系统资源分配最小单元;线程:操作系统任务调度最小单元。 一个进程内部可以创建多个线程,所有线程共享进程的堆、全局变量、静态变量、代码段;每个线程拥有自己独立栈空间(默认 8MB)。
- 线程局部变量(函数内局部变量)存放在线程私有栈,线程之间互不干扰;
- 全局 / 静态 / 堆上的数据多线程共享,会带来线程安全、竞态问题。
表格
| 进程 API | 对应线程 API |
|---|---|
| fork () 创建子进程 | pthread_create () 创建线程 |
| exit () 结束进程 | pthread_exit () 结束线程 |
| wait ()/waitpid () 回收子进程 | pthread_join () 回收线程 |
⚠️编译线程代码时,必须链接 pthread 库:
gcc demo.c -o demo -pthread
1.pthread_create 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数解析:
thread:输出参数,保存新线程 TID(线程 ID)attr:线程属性,栈大小、分离属性、优先级;填NULL使用默认属性start_routine:线程执行函数,函数格式固定:void* fun(void* arg)arg:传给线程函数的参数,无参数传NULL
返回值:成功返回 0;失败返回错误编号。
2.pthread_exit 线程退出
void pthread_exit(void *retval);
- 在线程函数内部调用,结束当前线程;
retval是线程退出返回值,可以被pthread_join获取;
注意:主线程调用
pthread_exit()只会结束主线程,不会终止整个进程;如果主线程 return/exit (),整个进程和所有线程全部销毁。
3.pthread_join 阻塞回收线程
int pthread_join(pthread_t thread, void **retval);
- 作用:阻塞等待指定线程结束,回收线程资源;
thread:要回收线程 TID;retval:接收线程pthread_exit()传出返回值,不需要则填 NULL;- 特点:阻塞函数,线程没执行完,调用者就卡住等待。
完整线程 Demo
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//线程函数,固定格式
void *thread_func(void *arg)
{
pthread_t tid = pthread_self();
printf("子线程(TID:%lu)开始执行\n",tid);
sleep(2);
printf("子线程即将退出\n");
pthread_exit((void*)100); //线程返回值
}
int main(void)
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,thread_func,NULL);
if(ret != 0){
perror("pthread_create");
return -1;
}
void *res;
//阻塞等待子线程结束
pthread_join(tid,&res);
printf("主线程:子线程退出,返回值:%ld\n",(long)res);
return 0;
}
编译运行命令:
gcc pthread_demo.c -o pthread_demo -pthread
./pthread_demo
五、拓展作业思路(文档原题作业)
需求:创建 4 个线程
- 采集线程:打印采集线程 TID,每 1s 打印正在采集;
- 存储线程:打印存储线程 TID,每 2s 打印正在存储;
- 显示线程:打印显示线程 TID,每 5s 打印正在显示;
- 日志线程:打印日志线程 TID,每 10s 打印正在日志记录;
- 主线程读取终端输入字符:
a/A:采集线程开启 / 停止打印b/B:存储线程开启 / 停止打印c/C:显示线程开启 / 停止打印d/D:日志线程开启 / 停止打印
实现思路提示:
- 设置 4 个全局 flag 变量,控制每个线程是否打印输出;
- 4 个线程内部循环 sleep,flag 为 1 就打印,flag 为 0 就跳过输出;
- 主线程循环
getchar()读取键盘字符,修改对应 flag 的值; - 主线程调用
pthread_join等待 4 个子线程。
注意:多线程修改全局共享变量,实际项目中需要加互斥锁,避免竞态。
六、总结
waitpid可以指定回收子进程,支持非阻塞 WNOHANG,用来处理僵尸进程;- exec 族会覆盖当前进程地址空间,成功之后后续代码不运行;后缀 l/v/p/e 记住参数传递方式;
- setenv 用来修改进程环境变量,子进程默认继承父进程环境变量;
- 线程是调度最小单元,共享进程大部分资源,每个线程私有栈;编译必须加
‑pthread; pthread_create创建线程,pthread_exit退出线程,pthread_join阻塞回收线程。
本篇内容来自 Linux+C 开发学习笔记,适合嵌入式软件工程师入门,如有错误欢迎评论区指正。
更多推荐



所有评论(0)