一 进程结束
这段代码的主要功能是创建一个子进程,子进程休眠 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,出错时返回 -1pid = fork();// 检查 fork 函数是否调用失败if (-1 == pid){// 如果失败,调用自定义的错误信息输出宏 ERR_MSG 输出错误信息ERR_MSG("fail to fork");// 程序返回 -1 表示出错return -1;}// 判断当前是子进程还是父进程// 如果 pid 为 0,说明当前是子进程if (0 == pid){// 输出子进程开始执行的信息,同时输出子进程的 PIDprintf("子进程(PID:%d)开始执行\n", getpid());// 子进程暂停执行 10 秒sleep(10);// 子进程以退出状态码 10 结束exit(10);}// 如果 pid 大于 0,说明当前是父进程else if (pid > 0){// 输出父进程开始执行的信息,同时输出父进程的 PIDprintf("父进程(PID:%d)开始执行\n", getpid());// 父进程调用 wait 函数等待子进程结束// wait 函数会阻塞父进程,直到有一个子进程结束,并将子进程的退出状态存储在 status 中// wait 函数返回结束的子进程的 PIDret = wait(&status);// 输出父进程回收子进程空间的信息,同时输出回收的子进程的 PIDprintf("回收到子进程(PID:%d)空间了\n", ret);// 检查子进程是否正常结束if (WIFEXITED(status)){// 如果正常结束,使用 WEXITSTATUS 宏获取子进程的退出状态码并输出printf("正常结束,值:%d\n", WEXITSTATUS(status));}// 检查子进程是否是因为收到信号而异常结束else if (WIFSIGNALED(status)){// 如果是被信号杀死,使用 WTERMSIG 宏获取杀死子进程的信号编号并输出printf("被 %d 号信号杀死\n", WTERMSIG(status));}}// 主函数正常返回 0return 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!" 以及当前进程的进程 IDprintf("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上面!" 以及当前进程的进程 IDprintf("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 函数返回的进程 IDpid_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,出错时返回 -1pid = fork();// 检查 fork 函数是否调用失败if (-1 == pid){// 调用自定义的错误处理函数 ERR_MSG 打印错误信息// 该函数通常会输出错误信息到标准错误输出ERR_MSG("fail to fork");// 程序出错,返回 -1 表示异常退出return -1;}// 判断是否为子进程if (0 == pid){// 子进程中调用 execlp 函数执行 ls -l 命令// execlp 函数会用新的程序替换当前进程的映像// 第一个参数 "ls" 是要执行的程序名,后续参数是传递给该程序的命令行参数,最后一个参数必须为 NULLexeclp("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,用于存储新创建线程的线程IDpthread_t tid;// 定义一个整型变量 ret,用于存储函数调用的返回值,初始化为 0int 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());// 线程执行完毕,返回 NULLreturn NULL;
}// 主函数,程序的入口点
int main(int argc, const char **argv)
{// 定义一个包含 3 个 pthread_t 类型元素的数组,用于存储创建的线程 IDpthread_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;
}