访问【WRITE-BUG数字空间】_[内附完整源码和文档]
1 总体设计思路
Linux内核是单体式结构,相对于微内核结构而言,其运行效率高,但是系统的可维护性和可扩展性较差。为此,Linux提供了内核模块(module)机制,它不仅可以弥补单体式内核相对于微内核的一些不足,而不影响系统性能。内核模块的全称是动态可加载内核模块(Loadabe Kernel Module,KLM),简称为模块。模块是一个目标文件,能完成某种独立的功能,但其自身不是一个独立的进程,不能单独运行,可以动态载入内核,使其成为内核代码的一部分,与其他内核代码的地位完全相同,当不需要某模块功能时,可以动态卸载。实际上,Linux中大多数设备驱动程序或文件系统都以模块方式实现,因为它们数目繁多,体积庞大,不适合直接编译在内核中,而是通过模块机制,需要时临时加载。使用模块机制的另一个好处是,修改模块代码后只需要重新编译和加载模块,不必重新编译整个内核和引导系统,减少了更新系统功能的复杂度。
一个模块通常有一组函数和数据结构组成,用来实现某种功能,如实现一种文件系统、一个驱动模块或其他内核上层的功能。模块自身不是一个独立的进程,当前集成运行过程中调用到模块代码时,可以认为该段代码就代表当前进程在核心态运行。
模块编程可以使用内核的一些全局变量和函数,内核符号表就是用来存放所有模块都可以访问的符号及相应地址的表,存放在/proc/kallsyms文件中,可以使用“cat /proc/kallsyms”命令查看当前环境下导出的内核符号。
通常情况下,一个模块只需实现自己的功能,而无需导出任何符号;但如果其他模块需要调用这个模块的函数或数据结构时,该模块也可以导出符号。这样,其他模块可以使用由该模块导出的符号,利用现成的代码实现更加复杂的功能,这种技术也被称为模块层叠技术,当前已经使用在很多主流的内核源代码中。
2 主要函数的接口设计
module1:设计一个模块,要求列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID
module2:设计一个带参数的模块,其参数为某个进程的PID号,模块的功能时列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号、进程状态
模块中包括两个函数:
定义module_init()函数初始化模块、定义module_exit()函数卸载模块
对于需要传递参数的模块,我们使用module_param()来传递参数
task_struct是Linux内核的一种数据结构,它会被装载到RAM中并且包含着进程的信息。每个进程都把它的信息放在 task_struct 这个数据结构体,task_struct 包含了这些内容:
标示符 : 描述本进程的唯一标识符,用来区别其他进程
状态 :任务状态,退出代码,退出信号等
优先级 :相对于其他进程的优先级
程序计数器:程序中即将被执行的下一条指令的地址
内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据:进程执行时处理器的寄存器中的数据
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
有关进程信息还有以下三点需要了解:
保存进程信息的数据结构叫做 task_struct,可以在 include/linux/sched.h 中找到它
所有运行在系统中的进程都以 task_struct 链表的形式存在内核中
进程的信息可以通过 /proc 系统文件夹查看。要获取PID为400的进程信息,你需要查看 /proc/400 这个文件夹。大多数进程信息同样可以使用top和ps这些用户级工具来获取