一、printk的用法
例:
printk(KERN_ALERT "Hello,World!\n");
printk的用法与printf的用法差不多,
上面的例子改为printf:
printf("Hello,World!\n");
两个的不同点在于:一、printk有个表示日志级别的参数,比如上面的
KERN_ALERT。二、printk必须有一个换行符(\n),否则不能打印出来。
详细说明日志级别参数。
为什么要多加一个日志级别参数呢?我个人的理解是:printk是内核提供的函
数,也就是说它一般是运行在内核态的。内核的运行要求稳定但不失提供必须的信
息给用户。试想如果在内核中有两个信息要传达给用户,一个提示:硬盘烂了,不
可用;另一个提示:Hello, World!一个是关于系统生死的信息,一个不痛不痒的信
息,我们更关心哪个信息?如果我们一样对待这样的信息,一样让他们一起被打印
给用户,那有时候重要的信息,我们不得不采取的行动我们不能在第一时间做出反
映,因为我们要找出这些特殊的信息,我们不得不在一大堆无用的信息里寻找。如
果我们能把信息分类,重要的信息直接打印在你面前,如Hello, World般不痛不痒
的信息,让它找一地方睡着,哪天想看看把它翻出来就是了。这里只是简单地说明
为何在增加日志级别,更复杂的东西我谈不来,但这样也足够理解为何需要了。
内核提供了哪些日志级别?在头文件中定义了八个可用的日
志级别。
- #define KERN_EMERG "<0>" /* system is unusable */
- #define KERN_ALERT "<1>" /* action must be taken immediately */
- #define KERN_CRIT "<2>" /* critical conditions */
- #define KERN_ERR "<3>" /* error conditions */
- #define KERN_WARNING "<4>" /* warning conditions */
- #define KERN_NOTICE "<5>" /* normal but significant condition */
- #define KERN_INFO "<6>" /* informational */
- #define KERN_DEBUG "<7>" /* debug-level messages */
#define KERN_EMERG "<0>" /* system is unusable */#define KERN_ALERT "<1>" /* action must be taken immediately */#define KERN_CRIT "<2>" /* critical conditions */#define KERN_ERR "<3>" /* error conditions */#define KERN_WARNING "<4>" /* warning conditions */#define KERN_NOTICE "<5>" /* normal but significant condition */#define KERN_INFO "<6>" /* informational */#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必须
包含.整个过程的步骤如下:
1、实现当我们读取/proc下的文件时用于生成数据的函数。原型为:
- int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);
cdd中实现为:
- int cdd_read_procmem(char *buf, char **start, off_t offset,
- int count, int *eof, void *data)
- {
- int i, len = 0;
- int limit = count - 80;
- for (i = 0; i < cdd_nr_devs && len <= limit; i++){
- struct cdd_dev *d = &cddwishmiss[i];
- len += sprintf(buf+len,"\nDevice %i: data area %#x, size %i\n",i, d->data, d->size);
- }
- *eof = 1;
- printk(KERN_ALERT "Test proc\n");
- return len;
- }
int cdd_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data){ int i, len = 0; int limit = count - 80; for (i = 0; i < cdd_nr_devs && len <= limit; i++){ struct cdd_dev *d = &cddwishmiss[i]; len += sprintf(buf+len,"\nDevice %i: data area %#x, size %i\n",i, d->data, d->size); } *eof = 1; printk(KERN_ALERT "Test proc\n"); return len;}
buf参数指向内核创建的用于存放读取的数据的缓冲区,start参数在实现小文件时
可以直接用NULL。在cdd的实现中这些参数大都不用处理。
2、绑定数据生成函数与在要proc下生成的文件。用到的函数是:
- struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode, struct proc_dir_entry *base,
- read_proc_t *read_proc, void *data);
struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode, struct proc_dir_entry *base,read_proc_t *read_proc, void *data);
cdd中的实现为:
- create_proc_read_entry("cddmem", 0, NULL,cdd_read_procmem, NULL);
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输出关于设备驱动模块的信息。这样的方
法比较简单,输出的信息一般比较小。
实验结果:
- [birdb@wishmiss cdd]$ sudo ./cdd_load
- [birdb@wishmiss cdd]$ ls -l /proc/cddmem
- -r--r--r-- 1 root root 0 7月 10 22:11 /proc/cddmem
- [birdb@wishmiss cdd]$ cat /proc/cddmem
- Device 0: data area 0xf1e06800, size 0
- Device 1: data area 0xf1e05400, size 0
- Device 2: data area 0xf1e04000, size 0
- Device 3: data area 0xf1e04400, size 0
- Device 4: data area 0xf1e07c00, size 0
- Device 5: data area 0xf1e07800, size 0
- [birdb@wishmiss cdd]$ echo wishmiss >/dev/cdd2
- [birdb@wishmiss cdd]$ cat /proc/cddmem
- Device 0: data area 0xf1e06800, size 0
- Device 1: data area 0xf1e05400, size 0
- Device 2: data area 0xf1e04000, size 9
- Device 3: data area 0xf1e04400, size 0
- Device 4: data area 0xf1e07c00, size 0
- Device 5: data area 0xf1e07800, size 0
- [birdb@wishmiss cdd]$ echo wishmiss >/dev/cdd5
- [birdb@wishmiss cdd]$ cat /proc/cddmem
- Device 0: data area 0xf1e06800, size 0
- Device 1: data area 0xf1e05400, size 0
- Device 2: data area 0xf1e04000, size 9
- Device 3: data area 0xf1e04400, size 0
- Device 4: data area 0xf1e07c00, size 0
- Device 5: data area 0xf1e07800, size 9
[birdb@wishmiss cdd]$ sudo ./cdd_load [birdb@wishmiss cdd]$ ls -l /proc/cddmem -r--r--r-- 1 root root 0 7月 10 22:11 /proc/cddmem[birdb@wishmiss cdd]$ cat /proc/cddmemDevice 0: data area 0xf1e06800, size 0Device 1: data area 0xf1e05400, size 0Device 2: data area 0xf1e04000, size 0Device 3: data area 0xf1e04400, size 0Device 4: data area 0xf1e07c00, size 0Device 5: data area 0xf1e07800, size 0[birdb@wishmiss cdd]$ echo wishmiss >/dev/cdd2[birdb@wishmiss cdd]$ cat /proc/cddmem Device 0: data area 0xf1e06800, size 0Device 1: data area 0xf1e05400, size 0Device 2: data area 0xf1e04000, size 9Device 3: data area 0xf1e04400, size 0Device 4: data area 0xf1e07c00, size 0Device 5: data area 0xf1e07800, size 0[birdb@wishmiss cdd]$ echo wishmiss >/dev/cdd5[birdb@wishmiss cdd]$ cat /proc/cddmem Device 0: data area 0xf1e06800, size 0Device 1: data area 0xf1e05400, size 0Device 2: data area 0xf1e04000, size 9Device 3: data area 0xf1e04400, size 0Device 4: data area 0xf1e07c00, size 0Device 5: data area 0xf1e07800, size 9
附:
老大关于第四章的学习要点:
1. 理解printk的使用方法,并且熟悉7个消息级别的含义。
2. 怎样通过命令行,选择控制台显示特定级别的调试消息
3. 怎样用proc的接口,显示驱动中的一些信息,修改第三章的驱动作业,实现proc接口,显示自定义的调试信息。
4. 除以上内容以外的部分,可以概览,不要求深入理解。