Linux进程编程实例分析
一、基础进程创建与父子关系
1. 基本进程创建(fork.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t ret = fork();
if(ret>0)
{
while(1)
{
printf("发视频...\n");
sleep(1);
}
}
else if (0 == ret)
{
while(1)
{
printf("接收控制....\n");
sleep(1);
}
}
else // <0 -1
{
perror("fork");
return 1;
}
return 0;
}
要点:
-
典型的父子进程并发执行模式
-
通过fork()返回值区分父子进程
-
创建后父子进程独立运行
2. 父子进程变量共享(fork_var.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int a = 20;
int main()
{
pid_t ret = fork();
if(ret>0)
{
sleep(3);
printf("father a %d\n",a);
}
else if (0 == ret)
{
a+=10;
printf("child a is %d\n",a);
}
else // <0 -1
{
perror("fork");
return 1;
}
printf("a is %d\n",a);
return 0;
}
写时复制机制:
-
fork()后父子共享内存空间
-
子进程修改变量时,内核为其分配独立内存副本
-
父进程的变量值保持不变
3. 获取进程ID(getpid.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
int i=10;
pid_t ret = fork();
if(ret>0)
{
while(i--)
{
printf("发视频... pid:%d ppid:%d\n",getpid(),getppid());
sleep(1);
}
}
else if (0 == ret)
{
while(i--)
{
printf("接收控制....pid:%d ppid:%d\n",getpid(),getppid());
sleep(1);
}
}
else // <0 -1
{
perror("fork");
return 1;
}
printf("pid:%d ppid:%d\n",getpid(),getppid());
return 0;
}
// 获取并打印进程PID和父进程PPID
getpid(); // 当前进程ID
getppid(); // 父进程ID
二、进程终止与退出状态
1. exit() vs _exit()(exit.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("hello");
_exit(10);//exit(10);
printf("看见了,exit 就没退出\n");
return 0;
}
//
printf("hello"); // 输出"hello"
exit(10); // 刷新缓冲区,执行清理函数
//
printf("hello"); // 无输出(缓冲区未刷新)
_exit(10); // 直接退出,不刷新缓冲区
关键区别:
| 函数 | 缓冲区处理 | 清理函数 | 标准库调用链 |
|---|---|---|---|
| exit() | 刷新 | 执行atexit注册的函数 | exit→清理→_exit |
| _exit() | 不刷新 | 不执行 | 直接系统调用 |
2. exit状态码范围
-
正常范围:-128 ~ 127(8位)
-
使用WEXITSTATUS(status)获取
三、特殊进程状态
1. 僵尸进程(zombie.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if(pid>0)
{
while(1)
{
sleep(1);
}
}
else if(0 == pid)
{
printf("child is pid:%d\n",getpid());
exit(0);
}
else
{
perror("fork");
return 1;
}
return 0;
}
// 子进程:立即退出,成为僵尸进程
// 父进程:无限循环,不回收子进程状态
僵尸进程特征:
-
子进程已终止,但父进程未调用wait()
-
PCB未被释放,占用内核资源
-
可通过
ps aux | grep defunct查看
2. 孤儿进程(organ.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if(pid>0)
{
printf("father is pid:%d ppid:%d\n",getpid(),getppid());
exit(0);
}
else if(0 == pid)
{
int i =3;
while(i--)
{
printf("child is pid:%d ppid:%d\n",getpid(),getppid());
sleep(1);
}
}
else
{
perror("fork");
return 1;
}
return 0;
}
// 父进程:立即退出
// 子进程:循环3次,每次sleep(1)
// 现象:子进程的ppid变为1(被init进程收养)
孤儿进程处理:
-
父进程退出后,子进程被init进程接管
-
新父进程(init)负责资源回收
-
不会成为僵尸进程
四、进程状态回收
1. 基本回收(wait.c)
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if(pid>0)
{
printf("father pid:%d\n",getpid());
pid_t recycle = wait(NULL);
printf("recycle pid %d\n",recycle);
while(1)
{
sleep(1);
}
}
else if(0 == pid)
{
printf("child pid:%d\n",getpid());
sleep(3);
exit(1);
}
else
{
perror("fork");
return 1;
}
return 0;
}
// 父进程:阻塞等待任意子进程退出
wait(NULL); // 不关心退出状态
// 回收成功后继续执行
2. 带状态回收(wait2.c)
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();
if(pid>0)
{
printf("father pid:%d\n",getpid());
int status=0;// 退出状态值 32bit
pid_t recycle = wait(&status);
if(WIFEXITED(status)) //正常结束
{
printf("正常结束,recycle pid %d,子进程的退出值:%d\n",recycle,WEXITSTATUS(status));
}
if(WIFSIGNALED(status)) //异常结束
{
printf("异常结束,recycle pid %d,信号数:%d\n",recycle, WTERMSIG(status));
}
}
else if(0 == pid)
{
printf("child pid:%d\n",getpid());
sleep(10);
exit(50); // -128-127 退出值 8bit
}
else
{
perror("fork");
return 1;
}
return 0;
}
int status;
wait(&status); // 获取退出状态
// 状态检查宏:
WIFEXITED(status) // 是否正常退出
WEXITSTATUS(status) // 获取退出值(正常退出时)
WIFSIGNALED(status) // 是否信号终止
WTERMSIG(status) // 获取信号编号(信号终止时)
状态值存储结构:
-
status是32位整数 -
高16位:退出值/信号编号
-
低16位:退出原因标志位
五、多进程创建与控制
1. 创建多个子进程(many_child.c)
cpp
#include <stdio.h>
#include <unistd.h>
int main()
{
int n =5;
int i = 0 ;
printf("father pid:%d ppid:%d\n",getpid(),getppid());
for(i = 0 ;i<5;i++)
{
pid_t pid = fork();
if(pid>0)
{
continue;
}
else if (0 == pid)
{
printf("child pid:%d ppid:%d\n",getpid(),getppid());
return 0; // break;
}
else
{
perror("");
return 1;
}
}
return 0;
}
// 父进程循环创建5个子进程
// 关键:子进程创建后立即return,避免继续fork
六、进程数量监控(log_proc_num.c)
1. 功能说明
-
父子进程分别统计系统中进程数量
-
将统计结果和时间戳写入日志文件
-
父子进程执行频率不同(父3秒,子5秒)
2. 进程统计方法
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
int isnum(char* name)
{
while(*name)
{
if(*name>='0'&& *name<='9')
{
}
else
{
return 0;
}
name++;
}
return 1;
}
int proc_count(char *pathname)
{
DIR *dir = opendir(pathname);
if (NULL == dir)
{
perror("proc_count opendir\n");
return 1;
}
int count = 0;
while (1)
{
struct dirent *info = readdir(dir);
if (NULL == info)
{
break;
}
if(DT_DIR == info->d_type)
{
if(isnum(info->d_name))
{
count++;
}
}
}
closedir(dir);
return count;
}
int main(int argc, char *argv[])
{
FILE* fp = fopen("log","w");
if(NULL == fp)
{
perror("fopen");
return 1;
}
pid_t pid=fork();
if(pid>0)
{
int i = 0;
for(i=0;i<3;i++)
{
int num = proc_count("/proc");
time_t tm;
time(&tm);
struct tm *info = localtime(&tm);
fprintf(fp,"father, pid: %d proc_num:%d %d:%d:%d\n",getpid(),num,info->tm_hour,info->tm_min,info->tm_sec);
fflush(fp);
sleep(3);
}
}
else if (0 == pid)
{
int i = 0;
for(i=0;i<2;i++)
{
int num = proc_count("/proc");
time_t tm;
time(&tm);
struct tm *info = localtime(&tm);
fprintf(fp,"child, pid: %d proc_num:%d %d:%d:%d\n",getpid(),num,info->tm_hour,info->tm_min,info->tm_sec);
fflush(fp);
sleep(5);
}
}
else
{
perror("fork");
return 1;
}
return 0;
}
// 统计/proc目录下的数字目录名
// 每个数字目录对应一个进程
int proc_count(char *pathname) {
// 遍历/proc目录
// 检查目录名是否全为数字
// 统计有效进程目录数量
}
进程标识:
-
/proc/[pid]/- 进程信息目录 -
有效进程目录名均为数字
七、进程编程总结
1. 进程创建流程
-
调用fork()创建子进程
-
根据返回值区分父子进程
-
父子进程各自执行代码
-
子进程结束前向父进程传递退出状态
-
父进程调用wait()回收子进程
2. 常见问题与解决
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 僵尸进程 | 父进程未调用wait() | 父进程wait()回收 |
| 孤儿进程 | 父进程先退出 | 由init进程自动回收 |
| 缓冲区不刷新 | 使用_exit()退出 | 使用exit()或fflush() |
| 多进程循环fork | 子进程继续执行循环 | 子进程中break/return |
3. 最佳实践
-
资源回收:父进程应及时调用wait()回收子进程
-
状态检查:使用WIFEXITED等宏检查退出原因
-
进程同步:考虑进程间通信和同步需求
-
错误处理:检查fork()返回值,处理创建失败情况
-
日志记录:关键操作添加日志,便于调试
4. 系统调用对比
| 函数 | 类型 | 主要用途 |
|---|---|---|
| fork() | 系统调用 | 创建新进程 |
| exit() | 库函数 | 正常退出,清理资源 |
| _exit() | 系统调用 | 立即退出,不清理 |
| wait() | 系统调用 | 阻塞等待子进程结束 |
| getpid() | 系统调用 | 获取当前进程ID |
| getppid() | 系统调用 | 获取父进程ID |