Linux内核追踪
最近因为需要,需要跟踪调试内核,但是这个玩意一直是在使用,我从来是不报错是不会研究日志的。
当然这样的习惯已经不能用戳字来形容了,是非常的差劲的行为。今天来学习学习内核里面几个非正规的文件系统,实现特定的功能。
本文的全部内容来自飞哥的—>《奔跑吧,linux内核》入门篇
1、前言
很多内核开发者最喜欢的调试工具之一是printk。
printk是内核提供的格式化打印函数,它和C库所提供的printf()函数类似。
printk()函数和printf()函数的一个重要区别是前者提供打印等级,内核根据这个等级来判断是否在终端或者串口中打印输出。
但是其实就是我们平时调试一些逻辑的时候,我想知道这个代码到这一步的状态和功能,那就在这里整个printf。这个是异曲同工之妙
那为什么有了prink还需要整这些呢,这是因为你打印吧,你得在内核编译运行之前固定,这就不动态。因为我内核的东西在用户态我是确定和看不到的。
2、proc
2.1 理论
在早期的Linux内核中是没有proc和sys这两个目录的,调试参数时显得特别麻烦,只能靠个人对代码的理解程度。后来社区开发了一套虚拟的文件系统,也就是内核和内核模块用来向进程发送消息的机制,这个机制叫作proc。
**这个虚拟文件系统可以让用户和内核内部数据结构进行交互,**比如获取进程的有用信息、系统的有用信息等。可以查看某个进程的相关信息,也可以查看系统的信息,比如/proc/meminfo 用来查看内存的管理信息,/proc/cpuinfo用来观察CPU的信息。
**proc文件系统并不是真正意义上的文件系统,它存在内存中,并不占用磁盘空间。**它包含一些结构化的目录和虚拟文件,向用户呈现内核中的一些信息,也可以用作一种从用户空间向内核发送信息的手段。
如ps、top等很多shell命令正是从proc系统中读取信息,且更具可读性。
(原来这些命令是从这里来的,学习底层的东西会让你对很多事情的工作和原理有个了解,知其所以然)
2.2 实操
procfs文件系统提供了一些常用的API,这些API函数定义在fs/proc/internal.h文件中。
proc_mkdir()可以在 parent 父目录中创建一个名字为 name 的目录,如果 parent 指定为NULL,则在/proc的根目录下面创建一个目录。
(这个玩意要是你不执行,它就直接创建在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指向该文件的操作函数。
比如misc驱动在初始化时就创建了一个名为“misc”的文件。
<driver/char/misc.c> static int __init misc_init(void) { int err; #ifdef CONFIG_PROC_FS proc_create("misc", 0, NULL,&misc_proc_fops); #endif … }
proc_fops会指向该文件的操作函数集,比如misc驱动中会定义misc_proc_fops函数集,里面有open、read、llseek、release等文件操作函数。
static const struct file_operations misc_proc_fops = { .owner = THIS_MODULE, .open = misc_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, };
3、sys
3.1 理论
为什么有了proc目录还要一个sys目录呢?
其实在Linux内核的开发阶段,很多内核模块,特别是驱动开发向proc目录中乱添加节点和目录,导致proc目录下面显得杂乱无章。另一个原因是,Linux 2.5开发期间设计了一套统一的设备驱动模型,这就诞生了sys这个新的虚拟文件系统。
(sys统一管理驱动设备?)
3.2 实操参考
**kobject_create_and_add()**函数会动态生成一个struct kobject数据结构,然后将其注册到sysfs文件系统中。
struct kobject *kobject_create_and_add(const char *name, struct kobject*parent)
其中,
- name就是要创建的文件或者目录的名称,
- parent指向父目录的kobject数据结构,若parent为NULL,说明父目录就是/sys目录。
**sysfs_create_group()**函数会在参数1的kobj目录下面创建一个属性集合,并且显示该集合的文件。
static inline int sysfs_create_group(struct kobject *kobj,const struct attribute_group *grp)
参数2—>attribute_group *grp 中描述的是一组属性类型,其数据结构定义如下。
<include/linux/sysfs.h> struct attribute_group { const char *name; umode_t (*is_visible)(struct kobject *, struct attribute *, int); struct attribute **attrs; struct bin_attribute **bin_attrs; };
其中struct attribute数据结构用于描述文件的属性。
举个栗子
/sys/kernel目录建立在内核源代码的kernel/ksysfs.c文件中。
static int __init ksysfs_init(void){ kernel_kobj kobject_create_and_add("kernel", NULL); … error = sysfs_create_group(kernel_kobj,&kernel_attr_group); return 0; }
这里 kobject_create_and_add()在/sys 目录下建立一个名为“kernel”的目录,然后sysfs_create_group()函数在该目录下面创建一些属性集合。
static struct attribute * kernel_attrs[] = { &fscaps_attr.attr, &uevent_seqnum_attr.attr, &profiling_attr.attr, NULL }; static struct attribute_group kernel_attr_group = { .attrs = kernel_attrs, };
以profiling文件为例,这里实现profiling_show()和profiling_store()两个函数,分别对应读和写操作。
static ssize_t profiling_show(struct kobject *kobj,struct kobj_attribute *attr, char *buf){ return sprintf(buf, "%d\n", prof_on); } static ssize_t profiling_store(struct kobject *kobj,struct kobj_attribute *attr,const char *buf, size_t count){ int ret; profile_setup((char *)buf); ret = profile_init(); return count; } KERNEL_ATTR_RW(profiling);
其中KERNEL_ATTR_RW宏定义如下。
#define KERNEL_ATTR_RO(_name) \ static struct kobj_attribute _name##_attr =__ATTR_RO(_name) #define KERNEL_ATTR_RW(_name) \ static struct kobj_attribute _name##_attr =\ __ATTR(_name, 0644, _name##_show,_name##_store)
上面是/sys/kernel 的一个例子,Linux 内核源代码里还有很多设备驱动的例子。
4、debufs
4.1 理论
debugfs是一种用来调试内核的内存文件系统,内核开发者可以通过debugfs和用户空间交换数据,有点类似于前文提到的procfs和sysfs。debugfs文件系统也并不是存储在磁盘中,而是建立到内存中。
内核调试所使用的最原始的调试手段是添加打印语句,但是有时我们需要在运行中修改某些内核的数据,这时printk就显得无能为力了。(前面提到对于动态打印的需求)
一个可行的办法就是修改内核代码并编译,然后重新运行,但这种办法低效并且有些场景下系统还不能重启,那就需要一个临时的文件系统可以把关心的数据映射到用户空间。
之前内核实现的procfs和sysfs可以达到这个目的,但是procfs是为了反映系统以及进程的状态信息,sysfs用于Linux设备驱动模型,
把私有的调试信息加入这两个虚拟文件系统不太合适,因此内核多添加了一个虚拟文件系统,也就是debugfs。
所以说有了这个玩意。要知道为什么诞生的。
(到了这里你可能还会有点疑惑,什么东西啊,这个动态,我觉得就是配置参数,就是对于内核的东西可以通过这个写的方式去修改一些内核相关的参数与数据,然后达到我们的目的。调试调试那不得多试试。)
4.2 实操参考
debufs文件系统中有不少API函数可以使用,它们定义在include/linux/debugfs.h头文件中。
struct dentry *debugfs_create_dir(const char *name,struct dentry *parent) void debugfs_remove(struct dentry *dentry) struct dentry *debugfs_create_blob(const char *name, umode_t mode,struct dentry *parent,struct debugfs_blob_wrapper *blob) struct dentry *debugfs_create_file(const char *name, umode_t mode,struct dentry *parent, void *data,const struct file_operations *fops)