Linux内核学习(九):linux内核的特殊文件系统-debugfs、ftrace、sys
在内核跑起来之前,有些前置知识需要了解一下,这些可以作为我们的工具,更加帮助我们以后查看内核的信息与调试。
文章内容全部来自《奔跑吧 Linux内核》
1、prink()
在内核中有个经典的打印函数prink(),很多问题定位与追踪都有用到这个工具,这是很多内核开发者最喜欢的调试工具之一是printk。
printk是内核提供的格式化打印函数,它和C库所提供的printf()函数类似。
printk()函数和printf()函数的一个重要区别是前者提供打印等级,内核根据这个等级来判断是否在终端或者串口中打印输出。
**动态输出(Dynamic Printk)是内核子系统开发者最喜欢的输出手段之一。**在系统运行时,**动态输出可以由系统维护者动态打开那些内核子系统的输出,也可以有选择性地打开某些模块的输出,而printk是全局的,只能设置输出等级。
**要使用动态输出,必须在内核配置时打开CONFIG_DYNAMIC_DEBUG宏。**内核代码里使用了大量的pr_debug()/dev_dbg()函数来输出信息,这些就使用了动态输出技术,**另外还需要系统挂载debugfs文件系统。(其实这里就设计到了debugfs)
(动态输出的意思就是你可以想打开看谁就看谁,但是对于全局的来说,你设置了这个函数,它就肯定会打印,不够灵活。)
动态输出在debugfs文件系统中有一个control文件节点。文件节点记录了系统中所有使用动态输出技术的文件名路径、输出所在的行号、模块名字和要打印的语句。
2、proc
在早期的Linux内核中是没有proc,调试参数时显得特别麻烦,只能靠个人对代码的理解程度。后来社区开发了一套虚拟的文件系统,也就是内核和内核模块用来向进程发送消息的机制,这个机制叫作proc。
这个虚拟文件系统可以让用户和内核内部数据结构进行交互,比如获取进程的有用信息、系统的有用信息等。
可以查看某个进程的相关信息,也可以查看系统的信息,比如/proc/meminfo 用来查看内存的管理信息,/proc/cpuinfo用来观察CPU的信息。
proc文件系统并不是真正意义上的文件系统,它存在内存中,并不占用磁盘空间。它包含一些结构化的目录和虚拟文件,向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。
这些虚拟文件使用查看命令查看时会返回大量信息,但文件本身的大小却显示为0字节。
此外,这些特殊文件中大多数文件的时间及日期属性通常为当前系统时间和日期。事实上,如ps、top等很多shell命令正是从proc系统中读取信息,且更具可读性。
proc文件系统常用的一些节点如下。
- /proc/cpuinfo:CPU 的信息(型号、家族、缓存大小等)。
- /proc/meminfo:物理内存、交换空间等的信息。
- /proc/mounts:已加载的文件系统的列表。
- /proc/filesystems:被支持的文件系统。
- /proc/modules:已加载的模块。
- /proc/version:内核版本。
- /proc/cmdline:系统启动时输入的内核命令行参数。
- /proc//:表示进程的pid,这些子目录中包含可以提供有关进程的状态和环境的重要细节信息的文件。
- /proc/interrupts:中断使用情况。
- /proc/kmsg:内核日志信息。
- /proc/devices:可用的设备,如字符设备和块设备。
- /proc/ slabinfo:slab系统的统计信息。
- /proc/uptime:系统正常运行时间。
进程常见的信息如下。
- attr:提供安全相关的属性。
- cgroup:进程所属的控制组。
- cmdline:命令行参数。
- environ:环境变量值。
- fd:一个包含所有文件描述符的目录。
- mem:进程的内存被利用情况。
- stat:进程状态。
- status:进程当前状态,以可读的方式显示。
- cwd:当前工作目录的链接。
- exe:指向该进程的执行命令文件。
- maps:内存映射信息。
- statm:进程内存使用信息。
- root:链接此进程的root目录。
- oom_adj、oom_score、oom_score_adj:用于OOM killer。
可以看出proc是一个与进程相关的文件系统。
2.1、使用:
procfs文件系统提供了一些常用的API,这些API函数定义在fs/proc/internal.h文件中。
proc_mkdir()可以在 parent 父目录中创建一个名字为 name 的目录,如果 parent 指定为NULL,则在/proc的根目录下面创建一个目录。
struct proc_dir_entry *proc_mkdir(const char *name,struct proc_dir_entry *parent)
proc_create()函数会创建一个新的文件节点。
struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent,const struct file_operations *proc_fops)
其中:
- name是该节点的名称,
- mode是该节点的访问权限,以UGO的模式来表示;
- parent和proc_mkdir()函数中的parent类型,指向父进程的proc_dir_entry对象;
- proc_fops指向该文件的操作函数。
2.2、使用栗子:
比如misc驱动在初始化时就创建了一个名为“misc”的文件。
proc_fops会指向该文件的操作函数集,比如misc驱动中会定义misc_proc_fops函数集,里面有open、read、llseek、release等文件操作函数。
(这个misc文件正是我们自己创造的)
至于进一步的操作,这些文件的操作的韩式是你自定义实现的。
3、sys
为什么有了proc目录还要一个sys目录呢?
其实在Linux内核的开发阶段,很多内核模块,特别是驱动开发向proc目录中乱添加节点和目录,导致proc目录下面显得杂乱无章。另一个原因是,Linux 2.5开发期间设计了一套统一的设备驱动模型,这就诞生了sys这个新的虚拟文件系统。
sys–>管理设备驱动的文件系统
这个新的设备模型是为了对计算机上的所有设备进行统一地表示和操作,包括设备本身和设备之间的连接关系。
这个模型建立在 PCI 和 USB 的总线枚举过程的分析之上,这两个总线类型能代表当前系统中的大多数设备类型。
现在很多子系统、设备驱动程序已经将sysfs作为与用户空间友好的接口。
因此,系统中整体信息可通过procfs来获取,设备模型相关信息可通过sysfs来获取。
3.1、使用:
kobject_create_and_add()函数会动态生成一个struct kobject数据结构,然后将其注册到sysfs文件系统中。
其中,name就是要创建的文件或者目录的名称,parent指向父目录的kobject数据结构,若parent为NULL,说明父目录就是/sys目录。
struct kobject *kobject_create_and_add(const char *name, struct kobject*parent)
sysfs_create_group()函数会在参数1的kobj目录下面创建一个属性集合,并且显示该集合的文件。
static inline int sysfs_create_group(struct kobject *kobj,const struct attribute_group *grp)
参数2中描述的是一组属性类型,其数据结构定义如下。
struct attribute数据结构用于描述文件的属性。
/sys/kernel目录建立在内核源代码的kernel/ksysfs.c文件中。
这里 kobject_create_and_add()在/sys 目录下建立一个名为“kernel”的目录,然后sysfs_create_group()函数在该目录下面创建一些属性集合。
以profiling文件为例,这里实现profiling_show()和profiling_store()两个函数,分别对应读和写操作。
(源码还是自己在本地读者舒服,当你不知道这个是什么意思的时候,就点进去看看)
4、debugfs
debugfs是一种用来调试内核的内存文件系统,内核开发者可以通过debugfs和用户空间交换数据,有点类似于前文提到的procfs和sysfs。
debugfs文件系统也并不是存储在磁盘中,而是建立到内存中。
内核调试所使用的最原始的调试手段是添加打印语句,但是有时我们需要在运行中修改某些内核的数据,这时printk就显得无能为力了。
一个可行的办法就是修改内核代码并编译,然后重新运行,但这种办法低效并且有些场景下系统还不能重启,那就需要一个临时的文件系统可以把关心的数据映射到用户空间。
之前内核实现的procfs和sysfs可以达到这个目的,但是procfs是为了反映系统以及进程的状态信息,sysfs用于Linux设备驱动模型,把私有的调试信息加入这两个虚拟文件系统不太合适,因此内核多添加了一个虚拟文件系统,也就是debugfs。
debugfs一般会挂载到/sys/kernel/debug目录,可以通过mount命令来实现。# mount -t debugfs none /sys/kernel/debug
其实使用的方式和proc那些都很相似
4.1、举个栗子
my_debugTest = debugfs_create_dir("debugTest", NULL);
debugfs_create_u8("debugTestFs", 0644, my_debugTest,NULL, &fileop);
其实关键的信息传输功能在于fileop这个结构体对应的的对文件操作的函数,这两步只是建立好这个用户与内核调试的媒介,进一步的具体读写,在于对文件操作方法的实现。