1046 register_console(&s3c24xx_serial_console);//注册控制台
1901 static struct console s3c24xx_serial_console =
1902 {
1903 .name = S3C24XX_SERIAL_NAME,//控制台名称ttySAC
1904 .device = uart_console_device,//以后使用/dev/console时,用来狗仔设备节点
1905 .flags = CON_PRINTBUFFER,//控制台可以之前,printk已经在缓1907 冲区打印了,CON_PRINTBUFFER表示可以打印以前的信息了
1906 .index = -1,
1907 .write = s3c24xx_serial_console_write,//打印函数
1908 .setup = s3c24xx_serial_console_setup.//设置函数
1090 };
150 #define S3C24XX_SERIAL_NAME 'ttySAC'
大致看一下prink的函数过程,从调用过程可以看出打印的信息会存在log_buf这个变量中,而我们也可以通过查看cat /proc/kmsg来得到prink的数据,这句命令的作用就是从log_buf这个缓冲区中读出数据。
prink
vprintk
/* Emit the output into the temporary buffer */
// 先把输出信息放到临时的buffer
vscnprintf
// Copy the output into log_buf
// 把临时缓冲区的数据稍作处理(处理打印级别),再写入log_buf
// 比如prink('abc')会得到'<4>abc',再写入log_buf
// 可以用dmesg把log_buf里的数据打印出来重现内核的输出信息
// 调用硬件的write函数输出
release_console_sem();
call_console_drivers(_con_start, _log_end);
// 从log_buf得到数据,算出级别
_call_console_drivers(start_print, cur_index, msg_level);
// 如果够级别可以打印
__call_console_drivers(0, end & LOG_BUF_MASK);
con->write(con, &LOG_BUF(start), end - start);
2、prink的使用
prink的使用其实很简单,与我们平时使用C语言的printf差不多。一般如果在某个地方出错的话,会使用二分法的方法定位到最终的问题。下面举一个列子,引入了一个错误,我们在使用CPU的寄存器时没有使用虚拟地址,而是直接使用物理地址。
#include #include #include #include #include #include #include #include #include #include static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; static int first_drv_open(struct inode *inode, struct file *file) { //printk('first_drv_openn'); /* 配置GPF4,5,6为输出 */ printk(KERN_DEBUG'%s %s %dn', __FILE__, __FUNCTION__, __LINE__); *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); printk(KERN_DEBUG'%s %s %dn', __FILE__, __FUNCTION__, __LINE__); *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); printk(KERN_DEBUG'%s %s %dn', __FILE__, __FUNCTION__, __LINE__); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; //printk('first_drv_writen'); copy_from_user(&val, buf, count); // copy_to_user(); if (val == 1) { // 点灯 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6)); } else { // 灭灯 *gpfdat |= (1<<4) | (1<<5) | (1<<6); } return 0; } static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = first_drv_open, .write = first_drv_write, }; int major; static int first_drv_init(void) { printk(KERN_DEBUG'%s %s %dn', __FILE__, __FUNCTION__, __LINE__); major = register_chrdev(0, 'first_drv', &first_drv_fops); // 注册, 告诉内核 printk(KERN_DEBUG'%s %s %dn', __FILE__, __FUNCTION__, __LINE__); firstdrv_class = class_create(THIS_MODULE, 'firstdrv'); printk(KERN_DEBUG'%s %s %dn', __FILE__, __FUNCTION__, __LINE__); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, 'xyz'); /* /dev/xyz */ printk(KERN_DEBUG'%s %s %dn', __FILE__, __FUNCTION__, __LINE__); gpfcon = (volatile unsigned long *)0x56000050; //引入错误 gpfdat = gpfcon + 1; return 0; } static void first_drv_exit(void) { unregister_chrdev(major, 'first_drv'); // 卸载 class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); iounmap(gpfcon); } module_init(first_drv_init); module_exit(first_drv_exit); MODULE_LICENSE('GPL'); 当我们使用程序测试这个驱动时就会出错,出错时我们就利用printk(KERN_DEBUG'%s %s %dn', __FILE__, __FUNCTION__, __LINE__);这条打印语句定位到最终程序执行到哪里导致的出错,其中__FILE__可以打印出文件、__FUNCTION__可以打印出函数、__LINE__可以打印出哪一行。而KERN_DEBUG是打印等级,值越小打印等级越高。当我们调试成功后就不需要打印了,那么可以把打印等级的数值变大。而内核的设置的默认打印等级一般为4,我们可以通过两种方法更改打印等级。 cat /proc/sys/kernel/printk 更改这个文件可以改变打印级别 修改loglevel这个uboot传入的参数可以更改内核的打印级别
上一篇:Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程
下一篇:Linux驱动之一个简单的输入子系统程序编写
推荐阅读最新更新时间:2024-11-13 19:28
设计资源 培训 开发板 精华推荐
- 使用 Analog Devices 的 LTC3826EG-1 的参考设计
- 1323x 开发硬件,基于 MC1323x 器件的 MC1323x 开发平台
- LTM8046 的典型应用 - 3.1Vin 至 32Vin、2kVAC 隔离式 DC/DC 模块转换器
- RDR-242 - 30 W 电源
- OLED2020-08-11
- 使用 Analog Devices 的 LT1086CM-3.6 的参考设计
- 【航顺训练营】闭环步进电机控制器
- L7808A 功率 AM 调制器的典型应用(单位电压增益,IO = 0.5)
- 斯塔克灵魂注入器CH340C
- DC2141A,基于 LTM4647 30A DC/DC 降压稳压器的演示板