提示:基于此,使用递归,或使用带有几个
pid_t 变量(每个子进程一个)的 while
循环并明智地在子进程中运行的代码分支使用 continue
语句,可以很直接地实现本练习的逻辑。
作为此练习的答案,请展示您的程序在运行 4
代时(如果正确实现,应总共生成 15 个进程)的输出。
修改 kobject
示例模块:现在我们将回到使用这些思想进行内核模块设计。首先,从您的
linux 源码目录中,找到并将文件
samples/kobject/kobject-example.c
复制到您保存内核模块代码的目录中。这是一个使用称为 kobjects
的功能的内核模块,它提供了一个在内核和用户空间之间交换数据的接口。每个数据项称为一个属性(attribute),对于每个属性,您需要提供一个
show 和 store
函数,分别在用户空间读取和写入这些值时被调用。
switch (childPid = fork()) { case-1: errExit("fork"); case0: /* Child: immediately exits to become zombie */ printf("Child (PID=%ld) exiting\n", (long) getpid()); _exit(EXIT_SUCCESS); default: /* Parent */ sleep(3); /* Give child a chance to start and exit */ snprintf(cmd, CMD_SIZE, "ps | grep %s", basename(argv[0])); cmd[CMD_SIZE - 1] = '\0'; /* Ensure string is null-terminated */ system(cmd); /* View zombie child */
/* Now send the "sure kill" signal to the zombie */ if (kill(childPid, SIGKILL) == -1) errMsg("kill"); sleep(3); /* Give child a chance to react to signal */ printf("After sending SIGKILL to zombie (PID=%ld):\n", (long) childPid); system(cmd); /* View zombie child again */
有时,应用程序需要在进程终止时自动执行一些操作。考虑这样一个例子:一个应用程序库,如果在进程的生命周期中被使用,需要在进程退出时自动执行一些清理操作。由于该库无法控制进程何时以及如何退出,也不能强制主程序在退出前调用库特定的清理函数,因此无法保证清理一定会发生。在这种情况下,一种方法是使用退出处理程序(exit
handler)(较老的 System V 手册使用术语“程序终止例程”)。
staticvoidatexitFunc1(void){ printf("atexit function 1 called\n"); } staticvoidatexitFunc2(void){ printf("atexit function 2 called\n"); } staticvoidonexitFunc(int exitStatus, void *arg){ printf("on_exit function called: status=%d, arg=%ld\n", exitStatus, (long) arg); }
intmain(int argc, char *argv[]){ if (on_exit(onexitFunc, (void *) 10) != 0) fatal("on_exit 1"); if (atexit(atexitFunc1) != 0) fatal("atexit 1"); if (atexit(atexitFunc2) != 0) fatal("atexit 2"); if (on_exit(onexitFunc, (void *) 20) != 0) fatal("on_exit 2"); exit(2); }
当我们运行这个程序时,会看到以下输出:
1 2 3 4 5
$ ./exit_handlers on_exit function called: status=2, arg=20 atexit function 2 called atexit function 1 called on_exit function called: status=2, arg=10
Linux 拥有一个独特的线程实现。对 Linux
内核而言,没有“线程”的概念。Linux
将所有线程实现为标准进程。Linux
内核并不提供任何特殊的调度语义或数据结构来表示线程。相反,一个线程仅仅是一个与其他进程共享某些资源的进程。每个线程都有一个唯一的
task_struct,并且在内核看来就是一个普通的进程——线程只是恰巧与其他进程共享了资源(例如地址空间)。
这种线程实现方法与 Microsoft Windows 或 Sun Solaris
等操作系统形成了巨大对比,这些操作系统在内核中提供了对线程的明确支持(有时称线程为轻量级进程
(Lightweight Processes))。“轻量级进程”这个名字概括了 Linux
与其他系统在哲学上的差异。对这些其他操作系统而言,线程是一种抽象,旨在提供比笨重进程更轻量、更快速的执行单元。而对
Linux
而言,线程仅仅是进程之间共享资源的一种方式(而进程本身已经相当轻量)¹⁰。
例如,假设一个包含四个线程的进程。在具有显式线程支持的系统中,可能会存在一个进程描述符,该描述符又指向四个不同的线程。进程描述符描述共享资源,如地址空间或打开的文件。线程则描述它们独自拥有的资源。相反,在
Linux 中,简单地存在四个进程,因而有四个普通的 task_struct
结构。这四个进程被设置为共享某些资源。结果非常优雅。
¹⁰ 例如,可以对比 benchmark 一下 Linux
的进程创建时间与其他操作系统的进程(甚至线程!)创建时间。结果对 Linux
有利。