一 进程结束
这段代码的主要功能是创建一个子进程,子进程休眠 10 秒后以退出状态码 10 结束,父进程等待子进程结束并回收其资源,同时根据子进程的退出状态输出相应的信息。
int main(int argc, const char **argv)
{
// 定义一个 pid_t 类型的变量 pid,用于存储 fork 函数的返回值
pid_t pid;
// 定义一个 pid_t 类型的变量 ret,用于存储 wait 函数的返回值
pid_t ret;
// 定义一个整型变量 status,用于存储子进程的退出状态
int status;
// 调用 fork 函数创建一个新的子进程
// fork 函数会返回两次:在父进程中返回子进程的 PID,在子进程中返回 0,出错时返回 -1
pid = fork();
// 检查 fork 函数是否调用失败
if (-1 == pid)
{
// 如果失败,调用自定义的错误信息输出宏 ERR_MSG 输出错误信息
ERR_MSG("fail to fork");
// 程序返回 -1 表示出错
return -1;
}
// 判断当前是子进程还是父进程
// 如果 pid 为 0,说明当前是子进程
if (0 == pid)
{
// 输出子进程开始执行的信息,同时输出子进程的 PID
printf("子进程(PID:%d)开始执行\n", getpid());
// 子进程暂停执行 10 秒
sleep(10);
// 子进程以退出状态码 10 结束
exit(10);
}
// 如果 pid 大于 0,说明当前是父进程
else if (pid > 0)
{
// 输出父进程开始执行的信息,同时输出父进程的 PID
printf("父进程(PID:%d)开始执行\n", getpid());
// 父进程调用 wait 函数等待子进程结束
// wait 函数会阻塞父进程,直到有一个子进程结束,并将子进程的退出状态存储在 status 中
// wait 函数返回结束的子进程的 PID
ret = wait(&status);
// 输出父进程回收子进程空间的信息,同时输出回收的子进程的 PID
printf("回收到子进程(PID:%d)空间了\n", ret);
// 检查子进程是否正常结束
if (WIFEXITED(status))
{
// 如果正常结束,使用 WEXITSTATUS 宏获取子进程的退出状态码并输出
printf("正常结束,值:%d\n", WEXITSTATUS(status));
}
// 检查子进程是否是因为收到信号而异常结束
else if (WIFSIGNALED(status))
{
// 如果是被信号杀死,使用 WTERMSIG 宏获取杀死子进程的信号编号并输出
printf("被 %d 号信号杀死\n", WTERMSIG(status));
}
}
// 主函数正常返回 0
return 0;
}
二 exec函数族
hello.c
该文件定义了一个简单的 C 程序,其主要功能是打印一条问候信息、当前进程的进程 ID(PID),并遍历命令行参数列表,将每个参数的索引和值打印出来。
#include <stdio.h>
#include <unistd.h>
// main函数是程序的入口点
// argc 是命令行参数的数量,argv 是一个指向命令行参数字符串数组的指针
int main(int argc, char *argv[])
{
int i = 0; // 定义一个整型变量 i 并初始化为 0
// 打印 "hello world!" 以及当前进程的进程 ID
printf("hello world! PID:%d\n", getpid());
// 遍历命令行参数数组
for (i = 0; i < argc; i++)
{
// 打印每个命令行参数的索引和其值
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0; // 程序正常结束,返回 0
}
main.c
该文件定义了另一个 C 程序,其主要功能是使用 execv
系统调用执行 hello.c
编译生成的可执行文件 ./hello
,并传递一组命令行参数。如果 execv
调用失败,会输出错误信息。
#include <stdio.h>
#include "public.h"
// main函数是程序的入口点
// argc 是命令行参数的数量,argv 是一个指向命令行参数字符串数组的指针
int main(int argc, const char **argv)
{
// 定义一个字符指针数组 parg,用于存储要传递给 execv 函数的参数
char *parg[5] = {
"./hello", // 要执行的程序的路径
"world", // 第一个参数
"thank", // 第二个参数
"you", // 第三个参数
NULL, // 参数列表的结束标志
};
// 打印 "exec上面!" 以及当前进程的进程 ID
printf("exec上面! PID:%d\n", getpid());
// 使用 execv 函数执行 "./hello" 程序,并传递 parg 数组中的参数
// 如果 execv 函数执行成功,下面的代码将不会被执行
// 如果 execv 函数执行失败,将继续执行下面的代码
execv("./hello", parg);
// 打印错误信息,表示 execv 函数执行失败
ERR_MSG("fail to execl");
// 打印 "看到我就表示execl失败了"
printf("看到我就表示execl失败了\n");
return 0; // 程序正常结束,返回 0
}
三 system的用法
这段代码的主要功能是创建一个子进程,并在子进程中执行 ls -l
命令,父进程等待子进程结束后继续执行并打印提示信息。同时,代码中包含了一段被注释掉的用于打印环境变量的代码。
#include <stdio.h>
// 包含自定义的公共头文件,通常用于声明一些通用的宏、函数或结构体等
#include "public.h"
// 引入外部变量 environ,这是一个指向环境变量字符串数组的指针,
// 数组中的每个元素都是一个以 "变量名=值" 形式表示的环境变量
extern char **environ;
// 主函数,程序的入口点
// argc 表示命令行参数的数量,argv 是一个指向命令行参数数组的指针
int main(int argc, const char **argv)
{
// 定义一个整型变量 i,用于循环计数
int i = 0;
// 定义一个 pid_t 类型的变量 pid,用于存储 fork 函数返回的进程 ID
pid_t pid;
// 打印提示信息,表明程序执行到此处
printf("system上面!\n");
// #if 0 和 #endif 之间的代码块被注释掉,不会被编译执行
// 此代码块的作用是打印当前进程的所有环境变量
#if 0
// 打印分隔线,用于区分输出内容
printf("=========================================\n");
// 循环遍历 environ 数组,直到遇到 NULL 元素
for (i = 0; environ[i] != NULL; i++)
{
// 打印每个环境变量
printf("%s\n", environ[i]);
}
// 打印分隔线,用于区分输出内容
printf("=========================================\n");
#endif
// 注释掉的 system 函数调用,system 函数用于执行一个 shell 命令
// system 函数会创建一个子进程来执行指定的命令
// system("ls -l");
// 调用 fork 函数创建一个新的子进程
// fork 函数会返回两次,在父进程中返回子进程的进程 ID,在子进程中返回 0,出错时返回 -1
pid = fork();
// 检查 fork 函数是否调用失败
if (-1 == pid)
{
// 调用自定义的错误处理函数 ERR_MSG 打印错误信息
// 该函数通常会输出错误信息到标准错误输出
ERR_MSG("fail to fork");
// 程序出错,返回 -1 表示异常退出
return -1;
}
// 判断是否为子进程
if (0 == pid)
{
// 子进程中调用 execlp 函数执行 ls -l 命令
// execlp 函数会用新的程序替换当前进程的映像
// 第一个参数 "ls" 是要执行的程序名,后续参数是传递给该程序的命令行参数,最后一个参数必须为 NULL
execlp("ls", "ls", "-l", NULL);
}
// 判断是否为父进程
else if (pid > 0)
{
// 父进程中调用 wait 函数等待子进程结束
// wait 函数会阻塞父进程,直到任意一个子进程结束
// 这里传递 NULL 表示不关心子进程的退出状态
wait(NULL);
// 子进程结束后,父进程打印提示信息
printf("system下面!\n");
}
// 程序正常结束,返回 0 表示成功
return 0;
}
四 pthread线程使用
#include <stdio.h>
// 包含自定义的公共头文件,可能包含一些自定义的宏、函数声明或结构体定义等
#include "public.h"
// 定义线程函数,该函数将在新线程中执行
// 参数 arg 是传递给线程函数的参数,这里未使用
// 返回值为 void* 类型,是线程退出时返回的状态信息
void *thread_fun(void *arg)
{
// 打印线程开始执行的信息,使用 pthread_self() 函数获取当前线程的线程ID
// %#x 是格式化输出十六进制数,并带有 0x 前缀
printf("线程(TID:%#x)开始执行\n", (unsigned int)pthread_self());
// 线程函数执行完毕,返回 NULL 表示正常退出
return NULL;
}
// 主函数,程序的入口点
// argc 是命令行参数的数量,argv 是存储命令行参数的字符串数组
int main(int argc, const char **argv)
{
// 定义一个 pthread_t 类型的变量 tid,用于存储新创建线程的线程ID
pthread_t tid;
// 定义一个整型变量 ret,用于存储函数调用的返回值,初始化为 0
int ret = 0;
// 调用 pthread_create 函数创建一个新线程
// &tid 是用于存储新线程ID的变量的地址
// NULL 表示使用默认的线程属性
// thread_fun 是新线程要执行的函数
// NULL 表示不传递任何参数给新线程的函数
ret = pthread_create(&tid, NULL, thread_fun, NULL);
// 检查 pthread_create 函数的返回值
// 如果返回值不为 0,表示线程创建失败
if (ret != 0)
{
// 调用 ERR_MSG 宏输出错误信息,提示线程创建失败
ERR_MSG("fail to pthread_create");
// 线程创建失败,返回 -1 表示程序异常退出
return -1;
}
// 进入一个无限循环,使主线程一直运行
// 这里的无限循环会导致主线程一直占用 CPU 资源,直到程序被手动终止
while (1)
{
// 循环体为空,不执行任何操作
}
// 程序正常退出,返回 0 表示程序成功执行
return 0;
}
四 pthread线程创建
#include <stdio.h>
// 包含自定义的公共头文件,通常用于包含一些公共的函数声明、宏定义等
#include "public.h"
// 线程函数,该函数将作为线程的执行体
// 参数 arg 是传递给线程的参数,类型为 void*,可以传递任意类型的数据
void *thread(void *arg)
{
// 将传递进来的参数 arg 转换为 int 类型,并赋值给 num 变量
int num = (int)arg;
// 打印当前线程的编号和线程 ID(以十六进制格式输出)
printf("线程%d(TID:%#lx)正在执行\n", num, pthread_self());
// 线程执行完毕,返回 NULL
return NULL;
}
// 主函数,程序的入口点
int main(int argc, const char **argv)
{
// 定义一个包含 3 个 pthread_t 类型元素的数组,用于存储创建的线程 ID
pthread_t tid[3];
// 循环计数器
int i = 0;
// 定义一个包含 3 个整数的数组,用于存储传递给线程的参数
int a[3] = {1, 2, 3};
// 注释掉的代码,原本用于定义一个函数指针数组,指向不同的线程函数
// void *(*pfun[3])(void *) = {thread1, thread2, thread3};
// 循环创建 3 个线程
for (i = 0; i < 3; i++)
{
// 创建线程,将线程 ID 存储在 tid[i] 中
// NULL 表示使用默认的线程属性
// thread 是线程的执行体函数
// (void *)(i+1) 是传递给线程的参数,将 i+1 转换为 void* 类型
pthread_create(&tid[i], NULL, thread, (void *)(i+1));
}
// 循环等待 3 个线程执行完毕
for (i = 0; i < 3; i++)
{
// 等待 tid[i] 对应的线程执行完毕
// NULL 表示不获取线程的返回值
pthread_join(tid[i], NULL);
}
// 主函数返回 0,表示程序正常结束
return 0;
}