In continuation of the previous text:第二章:模块的编译与运行-4, let's GO ahead .
The Current Process
Although kernel modules don’t execute sequentially as applications do, most actions performed by the kernel are done on behalf of a specific process. Kernel code can refer to the current process by accessing the global item current, defined in <asm/current.h>, which yields a pointer to struct task_struct, defined by <linux/sched.h>. The current pointer refers to the process that is currently executing. During the exe-cution of a system call, such as open or read, the current process is the one that invoked the call. Kernel code can use process-specific information by using current, if it needs to do so. An example of this technique is presented in Chapter 6.
尽管内核模块不像应用程序那样按顺序执行,但内核执行的大多数操作都是代表特定进程进行的。内核代码可以通过访问全局项 current 来引用当前进程,该全局项定义在 <asm/current.h> 中,它会返回一个指向 struct task_struct 结构体的指针(该结构体由 <linux/sched.h> 定义)。
current 指针指向当前正在执行的进程。在系统调用(如 open 或 read)的执行过程中,current 所指的进程就是发起该调用的进程。如果需要,内核代码可以通过 current 来使用进程特定的信息。第 6 章会给出这种技术的一个示例。
补充说明:
-
struct task_struct:是 Linux 内核中描述进程的核心数据结构,包含了进程的 PID、状态、内存映射、打开的文件、优先级等所有与进程相关的信息,被称为 “进程描述符”。 -
current指针的实现:在不同架构中,current的实现方式不同(因此定义在<asm/current.h>中)。例如,x86 架构可能通过访问特定寄存器(如ESP栈指针)来快速定位当前进程的task_struct,而无需全局查找,以提高访问效率。 -
进程上下文的意义:当内核代码运行在 “进程上下文”(如处理系统调用)时,
current指针有效且指向具体进程,此时内核可以执行与进程相关的操作(如访问进程的用户空间内存、向进程发送信号)。而在中断上下文(如处理硬件中断)中,current指针可能无效或指向被中断的进程,此时内核代码通常不能执行依赖特定进程的操作。
Actually, current is not truly a global variable. The need to support SMP systems forced the kernel developers to develop a mechanism that finds the current process on the relevant CPU. This mechanism must also be fast, since references to current hap-pen frequently. The result is an architecture-dependent mechanism that, usually, hides a pointer to the task_struct structure on the kernel stack. The details of the implementation remain hidden to other kernel subsystems though, and a device driver can just include <linux/sched.h> and refer to the current process. For example,t he following statement prints the process ID and the command name of the current
process by accessing certain fields in struct task_struct:
printk(KERN_INFO "The process is \"%s\" (pid %i)\n",
current->comm, current->pid);
The command name stored in current->comm is the base name of the program file
(trimmed to 15 characters if need be) that is being executed by the current process.
实际上,current 并非真正的全局变量。由于需要支持对称多处理器(SMP)系统,内核开发者不得不设计一种机制,用于在当前运行的 CPU 上找到对应的当前进程。这种机制还必须具备高效性 —— 因为对 current 的引用极为频繁。
最终实现的是一种架构相关的机制:通常会将指向 task_struct 结构体的指针隐藏在内核栈中。不过,该实现的细节对其他内核子系统是透明的,设备驱动只需包含 <linux/sched.h> 头文件,就能直接引用当前进程。例如,以下语句通过访问 struct task_struct 中的特定字段,打印出当前进程的进程 ID(PID)和命令名:
printk(KERN_INFO "The process is \"%s\" (pid %i)\n",
current->comm, current->pid);
其中,current->comm 中存储的命令名,是当前进程正在执行的程序文件的基础名称(若长度超过 15 个字符,会被截断为 15 个字符)。
补充说明:
-
current非全局变量的核心原因在 SMP 系统中,多个 CPU 核心会同时运行不同进程,若current是全局变量,无法同时指向多个核心的 “当前进程”。因此内核通过 “内核栈绑定” 机制,让每个 CPU(或每个进程的内核栈)都关联自己的task_struct指针 —— 当代码在某个核心上执行时,从该核心对应的内核栈中提取task_struct地址,就能得到当前核心的 “当前进程”,确保 SMP 环境下的正确性。 -
内核栈与
task_struct的关联每个进程在进入内核态时,会使用独立的 “内核栈”(区别于用户态栈)。内核会将该进程的task_struct指针存放在内核栈的固定位置(如栈底或栈顶),访问current时,本质是通过栈指针(如 x86 的ESP寄存器)计算偏移,快速取出task_struct地址。这种方式无需全局查找,兼顾了效率与 SMP 兼容性。 -
current->comm与current->pid的作用-
current->pid:即进程的唯一标识符(PID),用于内核区分不同进程,也是用户态工具(如ps、kill)操作进程的依据。 -
current->comm:存储进程对应的程序名(如bash、ls),长度被限制为 15 个字符(源于早期内核的设计,后续版本虽有扩展,但仍保留兼容性限制),主要用于日志打印、调试时快速识别进程身份。
-
A Few Other Details
Kernel programming differs from user-space programming in many ways. We’ll point things out as we get to them over the course of the book, but there are a few fundamental issues which, while not warranting a section of their own, are worth a mention. So, as you dig into the kernel, the following issues should be kept in mind. Applications are laid out in virtual memory with a very large stack area. The stack, of course, is used to hold the function call history and all automatic variables created by currently active functions. The kernel, instead, has a very small stack; it can be as small as a single, 4096-byte page. Your functions must share that stack with the
entire kernel-space call chain. Thus, it is never a good idea to declare large auto-matic variables; if you need larger structures, you should allocate them dynamically at call time.
内核编程与用户空间编程在很多方面都存在差异。在本书后续内容中,我们会逐一指出这些差异,但有几个基础性问题值得在此一提(尽管它们不足以单独构成一个章节)。因此,当你深入学习内核时,需要牢记以下几点:
应用程序在虚拟内存中拥有非常大的栈空间。当然,栈用于保存函数调用历史以及当前活跃函数创建的所有自动变量。而内核的栈空间则非常小,可能只有一个 4096 字节的页那么大。你的函数必须与整个内核空间的调用链共享这个栈。因此,声明大型自动变量绝不是一个好主意;如果需要更大的结构体,应该在调用时动态分配它们。
补充说明:
-
内核栈大小的限制:内核栈的大小通常固定(如 x86 架构默认 8KB,部分场景下为 4KB),且由内核在进程创建时预先分配,无法像用户态栈那样动态增长。这是因为内核需要高效管理资源,且栈溢出在内核态会直接导致系统崩溃,风险极高。
-
大型自动变量的风险:自动变量(即函数内声明的非静态局部变量)存储在栈上,若声明过大(如
char buf[1024*10]),可能直接耗尽内核栈空间,引发 “栈溢出” 错误,导致当前进程崩溃甚至系统宕机。 -
动态分配的替代方案:内核提供了多种动态内存分配接口(如
kmalloc、vmalloc等),用于在堆上分配较大内存。这些接口会从内核管理的动态内存池(而非栈)中分配空间,避免栈空间不足的问题,但需注意动态内存分配可能失败,需做好错误处理。
Often, as you look at the kernel API, you will encounter function names starting with a double underscore (__). Functions so marked are generally a low-level component of the interface and should be used with caution. Essentially, the double underscore says to the programmer: “If you call this function, be sure you know what you are doing.”
在查看内核 API 时,你常会遇到以双下划线(__)开头的函数名。带有这种标记的函数通常是接口的底层组件,使用时需格外谨慎。本质上,双下划线是在向程序员传递这样的信息:“如果调用这个函数,务必确保自己清楚在做什么。”
Kernel code cannot do floating point arithmetic. Enabling floating point would require that the kernel save and restore the floating point processor’s state on each entry to, and exit from, kernel space—at least, on some architectures. Given that there really is no need for floating point in kernel code, the extra overhead is not worthwhile.
内核代码不能进行浮点运算。启用浮点运算意味着内核在每次进入和退出内核空间时,都需要保存和恢复浮点处理器的状态 —— 至少在某些架构上是这样。考虑到内核代码实际上并不需要浮点运算,这种额外的开销是不值得的。
补充说明:
-
双下划线函数的特性这类函数(如
__kmalloc、__list_add)通常是内核内部的 “私有” 接口,跳过了一些安全检查或参数验证,以追求更高性能。例如,标准的kmalloc会进行内存分配的安全性检查,而__kmalloc可能省略部分检查,直接执行底层分配逻辑。使用时若参数不当,极易引发内存错误,因此仅建议在明确知晓风险且需要极致性能的场景下使用。 -
内核禁用浮点运算的原因
-
状态保存开销:浮点处理器(FPU)有独立的寄存器和状态,用户态程序使用浮点运算后,其状态会保存在 FPU 中。若内核代码使用浮点运算,需在进入内核态时保存用户态的 FPU 状态,退出时恢复,这会显著增加系统调用和中断处理的开销。
-
无实际需求:内核的主要任务是管理资源、处理中断、提供系统调用等,这些工作通常可通过整数运算完成,无需浮点支持。即便偶尔需要小数计算(如某些驱动中的传感器数据转换),也可通过定点数模拟实现,避免使用浮点运算。
-
架构兼容性:不同 CPU 架构的 FPU 实现差异较大,内核若支持浮点运算,需适配各种架构,增加代码复杂度。
-
因此,内核代码中若出现浮点运算(如声明 float 或 double 变量、使用 / 进行非整数除法等),编译时会报错,这是内核设计的强制限制。

被折叠的 条评论
为什么被折叠?



