LDD3学习笔记《三》第四章

时间:2022-06-16 04:01:52

一、printk的用法
例:
  printk(KERN_ALERT "Hello,World!\n");

  printk的用法与printf的用法差不多,

上面的例子改为printf:

 

  printf("Hello,World!\n");

  两个的不同点在于:一、printk有个表示日志级别的参数,比如上面的
KERN_ALERT。二、printk必须有一个换行符(\n),否则不能打印出来。
  详细说明日志级别参数。
  为什么要多加一个日志级别参数呢?我个人的理解是:printk是内核提供的函
数,也就是说它一般是运行在内核态的。内核的运行要求稳定但不失提供必须的信
息给用户。试想如果在内核中有两个信息要传达给用户,一个提示:硬盘烂了,不
可用;另一个提示:Hello, World!一个是关于系统生死的信息,一个不痛不痒的信
息,我们更关心哪个信息?如果我们一样对待这样的信息,一样让他们一起被打印
给用户,那有时候重要的信息,我们不得不采取的行动我们不能在第一时间做出反
映,因为我们要找出这些特殊的信息,我们不得不在一大堆无用的信息里寻找。如
果我们能把信息分类,重要的信息直接打印在你面前,如Hello, World般不痛不痒
的信息,让它找一地方睡着,哪天想看看把它翻出来就是了。这里只是简单地说明
为何在增加日志级别,更复杂的东西我谈不来,但这样也足够理解为何需要了。
  内核提供了哪些日志级别?在头文件<linux/kernel.h>中定义了八个可用的日
志级别。

  1. #define KERN_EMERG      "<0>"   /* system is unusable                   */  
  2. #define KERN_ALERT      "<1>"   /* action must be taken immediately     */  
  3. #define KERN_CRIT       "<2>"   /* critical conditions                  */  
  4. #define KERN_ERR        "<3>"   /* error conditions                     */  
  5. #define KERN_WARNING    "<4>"   /* warning conditions                   */  
  6. #define KERN_NOTICE     "<5>"   /* normal but significant condition     */  
  7. #define KERN_INFO       "<6>"   /* informational                        */  
  8. #define KERN_DEBUG      "<7>"   /* debug-level messages                 */   

  这些可用的日志级别在我们调用printk显性地给出,如例子中的KERN_ALERT。
当然也可以这样用:printk("Hello, World!\n");这不是和printf没什么区别了
吗?有区别,因为如果没有给出日志级别参数,那么就采用默认的日志级别:
DEFAULT_MESSAGE_LOGLEVEL,在kernel/printk.c中定义。通过查看这些日志级别的
定义我们知道,它们就是宏定义,编译器在预处理阶段展开这些宏。例如
printk("Hello, World!\n");展开为:printk("<4>Hello, World!\n");
  有了这些日志级别,还不足以根据需要打印不同的信息到我们面前。还要行动
klogd和一个console_loglevel.klogd用于把信息传递到用户空间,
console_loglevel字面解释是控制台日志级别,用于选择哪些信息打印出来显现给
我们看。只有日志级别小于console_loglevel时,消息才会打印到当前控制台上。
(区别虚拟终端和控制台--简单地说我们在图形界面下开启的命令行终端便是一
个虚拟终端,我们用Ctrl+Alt+F?切换的就是控制台)不管怎么样,消息都不会打
印到虚拟终端。一般只打印到控制台。
二、通过命令行修改控制台日志级别显示不同级别的调试信息

 

我们可以修改控制台日志级别,书本也给出了三种修改方法:1、通过加-c选项重
新启动klogd,2、通过程序修改,并且提供了修改程序,3、通过文本文件修改。

这里我们只讲通过文本文件修改非常简单,只要一个命令就可以了:echo 8
>/proc/sys/kernel/printk。文本文件:/proc/sys/kernel/printk包含4个整数
值,分别是:当前的控制台日志级别,默认的日志级别,最小允许的日志级别,引
导时默认控制台日志级别。有时候我们的klogd启动了,printk的用法没有错,就
是不能在当前控制台上打印出消息,这时很可能与我们的控制台日志级别有关,我
们可以通过命令行直接修改当前控制台日志级别,把它的数值改大一点,这样就可
以显示大部分的日志级别消息。

  基本上理解了这些东西以后我们就理解了printk的用法,以及程序没错,却没
打印出消息来的原因了。书上提到的其他知识需要额外地资料,而书本却没有给,
所以我们只理解这些就足够了。

三、proc接口的实现。

我们可以通过proc导出我们的模块的信息。下面的每个文件绑定于一个内核函数,
当读取该文件时,写它绑定的函数会动态生成内容。在我的cdd实例中,我实现了
一个cdd_read_procmem函数,在proc下生成的文件是cddmem,用于生成关于cdd设
备的信息,包括每个设备的内存地址,数据区域数据的大小等。要使用/proc必须
包含<linux/proc_fs.h>.整个过程的步骤如下:
1、实现当我们读取/proc下的文件时用于生成数据的函数。原型为:

  1. int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);  

  cdd中实现为:
  1. int cdd_read_procmem(char *buf, char **start, off_t offset,  
  2.                 int count, int *eof, void *data)  
  3. {  
  4.         int i, len = 0;  
  5.         int limit = count - 80;  
  6.         for (i = 0; i < cdd_nr_devs && len <= limit; i++){  
  7.                 struct cdd_dev *d = &cddwishmiss[i];  
  8.                 len += sprintf(buf+len,"\nDevice %i: data area %#x, size %i\n",i, d->data, d->size);  
  9.         }  
  10.         *eof = 1;  
  11.         printk(KERN_ALERT "Test proc\n");  
  12.         return len;  
  13. }  

buf参数指向内核创建的用于存放读取的数据的缓冲区,start参数在实现小文件时
可以直接用NULL。在cdd的实现中这些参数大都不用处理。
2、绑定数据生成函数与在要proc下生成的文件。用到的函数是:
  1. struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode, struct proc_dir_entry *base,  
  2. read_proc_t *read_proc, void *data);  

 cdd中的实现为:
  1. create_proc_read_entry("cddmem", 0, NULL,cdd_read_procmem, NULL);  

当这个函数执行的时候就会在/proc下生成cddmem文件,当读取这个文件时,
cdd_read_procmem就会生成数据返回给我们。这个绑定函数在模块加载时执行。
3、当模块卸载时/proc中的入口项也应被删除,也就是模块加载时创建的cddmem,
这个工作由:remove_proc_entry("cddmem", NULL);完成。这个函数在模块卸载时
执行。

 

通过上面三个步骤就能实现了通过/proc输出关于设备驱动模块的信息。这样的方
法比较简单,输出的信息一般比较小。

实验结果:
  1. [birdb@wishmiss cdd]$ sudo ./cdd_load   
  2. [birdb@wishmiss cdd]$ ls -l /proc/cddmem   
  3. -r--r--r-- 1 root root 0  7月 10 22:11 /proc/cddmem  
  4. [birdb@wishmiss cdd]$ cat /proc/cddmem  
  5. Device 0: data area 0xf1e06800, size 0  
  6. Device 1: data area 0xf1e05400, size 0  
  7. Device 2: data area 0xf1e04000, size 0  
  8. Device 3: data area 0xf1e04400, size 0  
  9. Device 4: data area 0xf1e07c00, size 0  
  10. Device 5: data area 0xf1e07800, size 0  
  11. [birdb@wishmiss cdd]$ echo wishmiss >/dev/cdd2  
  12. [birdb@wishmiss cdd]$ cat /proc/cddmem   
  13. Device 0: data area 0xf1e06800, size 0  
  14. Device 1: data area 0xf1e05400, size 0  
  15. Device 2: data area 0xf1e04000, size 9  
  16. Device 3: data area 0xf1e04400, size 0  
  17. Device 4: data area 0xf1e07c00, size 0  
  18. Device 5: data area 0xf1e07800, size 0  
  19. [birdb@wishmiss cdd]$ echo wishmiss >/dev/cdd5  
  20. [birdb@wishmiss cdd]$ cat /proc/cddmem   
  21. Device 0: data area 0xf1e06800, size 0  
  22. Device 1: data area 0xf1e05400, size 0  
  23. Device 2: data area 0xf1e04000, size 9  
  24. Device 3: data area 0xf1e04400, size 0  
  25. Device 4: data area 0xf1e07c00, size 0  
  26. Device 5: data area 0xf1e07800, size 9  
附:
老大关于第四章的学习要点:
1. 理解printk的使用方法,并且熟悉7个消息级别的含义。

 

2. 怎样通过命令行,选择控制台显示特定级别的调试消息

3. 怎样用proc的接口,显示驱动中的一些信息,修改第三章的驱动作业,实现proc接口,显示自定义的调试信息。

4. 除以上内容以外的部分,可以概览,不要求深入理解。