嵌入式Linux驱动学习【15】—— 驱动调试printk

1 设置打印

  在uboot命令行设置打印的显示设备:
(1)LCD:console=tty1
(2)串口UART0:console=ttySAC0,115200

2 源码分析

2.1 命令行传参

  内核/kernel/printk.c中

__setup("console=", console_setup);

  根据命令行传入的参数,调用console_setup。

static int __init console_setup(char *str)
{
	char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
	char *s, *options, *brl_options = NULL;
	int idx;

#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
	if (!memcmp(str, "brl,", 4)) {
		brl_options = "";
		str += 4;
	} else if (!memcmp(str, "brl=", 4)) {
		brl_options = str + 4;
		str = strchr(brl_options, ',');
		if (!str) {
			printk(KERN_ERR "need port name after brl=\n");
			return 1;
		}
		*(str++) = 0;
	}
#endif

	/*
	 * Decode str into name, index, options.
	 */
	if (str[0] >= '0' && str[0] <= '9') {
		strcpy(buf, "ttyS");
		strncpy(buf + 4, str, sizeof(buf) - 5);
	} else {
		strncpy(buf, str, sizeof(buf) - 1);
	}
	buf[sizeof(buf) - 1] = 0;
	if ((options = strchr(str, ',')) != NULL)
		*(options++) = 0;
#ifdef __sparc__
	if (!strcmp(str, "ttya"))
		strcpy(buf, "ttyS0");
	if (!strcmp(str, "ttyb"))
		strcpy(buf, "ttyS1");
#endif
	for (s = buf; *s; s++)
		if ((*s >= '0' && *s <= '9') || *s == ',')
			break;
	idx = simple_strtoul(s, NULL, 10);
	*s = 0;

	__add_preferred_console(buf, idx, options, brl_options);
	console_set_on_cmdline = 1;
	return 1;
}

  以“console=ttySAC0,115200”为例,最终buf=“ttySAC”,idx=0,options=115200。

static int __add_preferred_console(char *name, int idx, char *options,
				   char *brl_options)
{
	struct console_cmdline *c;
	int i;

	/*
	 *	See if this tty is not yet registered, and
	 *	if we have a slot free.
	 */
	for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
		if (strcmp(console_cmdline[i].name, name) == 0 &&
			  console_cmdline[i].index == idx) {
				if (!brl_options)
					selected_console = i;
				return 0;
		}
	if (i == MAX_CMDLINECONSOLES)
		return -E2BIG;
	if (!brl_options)
		selected_console = i;
	c = &console_cmdline[i];
	strlcpy(c->name, name, sizeof(c->name));
	c->options = options;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
	c->brl_options = brl_options;
#endif
	c->index = idx;
	return 0;
}

  最终将信息存在了console_cmdline[]中,console_cmdline是全局数组。
  在register_console中有用到console_cmdline[]。以串口为例。

#define S3C24XX_SERIAL_NAME	"ttySAC"

static struct console s3c24xx_serial_console = {
	.name		= S3C24XX_SERIAL_NAME,    //"ttySAC"
	.device		= uart_console_device,
	.flags		= CON_PRINTBUFFER,
	.index		= -1,
	.write		= s3c24xx_serial_console_write,
	.setup		= s3c24xx_serial_console_setup
};

int s3c24xx_serial_initconsole(struct platform_driver *drv,
			       struct s3c24xx_uart_info *info)

{
	...

	register_console(&s3c24xx_serial_console);
	return 0;
}

  在register_console()里,便会通过“ttySAC”来匹配console_cmdline[i]的名称,当匹配成功,printk()调用的console结构体便是s3c24xx_serial_console了。

2.2 printk

  printk–>vprintk。
  vprintk中用到两个buff:printk_buf临时缓冲区,log_buf最终要输出的字符串。

asmlinkage int vprintk(const char *fmt, va_list args)
{
	...
	/* 传到缓冲区中 */
	printed_len += vscnprintf(printk_buf + printed_len,
				  sizeof(printk_buf) - printed_len, fmt, args);


	p = printk_buf;

	/* 判断打印级别 */
	if (p[0] == '<') {
		unsigned char c = p[1];
		if (c && p[2] == '>') {
			switch (c) {
			case '0' ... '7': /* loglevel */
				current_log_level = c - '0';
			/* Fallthrough - make sure we're on a new line */
			case 'd': /* KERN_DEFAULT */
				if (!new_text_line) {
					emit_log_char('\n');
					new_text_line = 1;
				}
			/* Fallthrough - skip the loglevel */
			case 'c': /* KERN_CONT */
				p += 3;
				break;
			}
		}
	}

	//拷贝printk_buf数据到环形缓冲区中
	for ( ; *p; p++) {
		if (new_text_line) {
			/* Always output the token */
			emit_log_char('<');
			emit_log_char(current_log_level + '0');
			emit_log_char('>');
			printed_len += 3;
			new_text_line = 0;
			//如果设置了此选项,则在每一条printk信息前都要加上时间参数
			if (printk_time) {
				/* Follow the token with the time */
				char tbuf[50], *tp;
				unsigned tlen;
				unsigned long long t;
				unsigned long nanosec_rem;

				t = cpu_clock(printk_cpu);
				nanosec_rem = do_div(t, 1000000000);
				tlen = sprintf(tbuf, "[%5lu.%06lu] ",
						(unsigned long) t,
						nanosec_rem / 1000);

				for (tp = tbuf; tp < tbuf + tlen; tp++)
					emit_log_char(*tp);
				printed_len += tlen;
			}

			if (!*p)
				break;
		}

		emit_log_char(*p);
		if (*p == '\n')
			new_text_line = 1;
	}

	
	if (acquire_console_semaphore_for_printk(this_cpu))
		release_console_sem();//将log_buf中的内容发送给console,并且唤醒klogd

	lockdep_on();
out_restore_irqs:
	raw_local_irq_restore(flags);

	preempt_enable();
	return printed_len;
}

  log_buff的环形缓冲区。

static unsigned log_start;    /* Index into log_buf: next char to be read by syslog() */
static unsigned con_start;    /* Index into log_buf: next char to be sent to consoles */
static unsigned log_end;    /* Index into log_buf: most-recently-written-char + 1 */

static void emit_log_char(char c)
{
	LOG_BUF(log_end) = c;
	log_end++;
	if (log_end - log_start > log_buf_len)
		log_start = log_end - log_buf_len;
	if (log_end - con_start > log_buf_len)
		con_start = log_end - log_buf_len;
	if (logged_chars < log_buf_len)
		logged_chars++;
}

  最终调用了release_console_sem,在此函数中完成console相关的操作。

release_console_sem --->
	call_console_drivers --->
		_call_console_drivers --->
			__call_console_drivers --->
				con->write                //即调用了s3c24xx_serial_console结构体的write函数

3 操作

(1)修改打印级别
  1)修改 /proc/sys/kernel/printk
  2)修改内核文件_call_console_drivers ()中的console_loglevel值
  3)uboot命令行参数loglevel=
(2)使用

printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

  dmesg命令来查看log_buf[],对应/proc/kmsg文件。也可使用cat命令,区别:
  1)dmesg打印出所有信息
  2)cat打印出每次新的环形缓冲区的信息

4 /proc/kmsg文件

  /proc/kmsg生成,在fs/proc中

static const struct file_operations proc_kmsg_operations = {
	.read		= kmsg_read,
	.poll		= kmsg_poll,
	.open		= kmsg_open,
	.release	= kmsg_release,
};

static int __init proc_kmsg_init(void)
{
	proc_create("kmsg", S_IRUSR, NULL, &proc_kmsg_operations);
	return 0;
}

  当执行cat /proc/kmsg时,读取缓冲区内容。

static ssize_t kmsg_read(struct file *file, char __user *buf,
			 size_t count, loff_t *ppos)
{
	if ((file->f_flags & O_NONBLOCK) && !do_syslog(9, NULL, 0))
		return -EAGAIN;
	return do_syslog(2, buf, count);
}

int do_syslog(int type, char __user *buf, int len)
{
	...
	case 2:		/* Read from log */
		error = -EINVAL;
		if (!buf || len < 0)
			goto out;
		error = 0;
		if (!len)
			goto out;
		if (!access_ok(VERIFY_WRITE, buf, len)) {
			error = -EFAULT;
			goto out;
		}
		error = wait_event_interruptible(log_wait,
							(log_start - log_end));
		if (error)
			goto out;
		i = 0;
		spin_lock_irq(&logbuf_lock);
		while (!error && (log_start != log_end) && i < len) {
			c = LOG_BUF(log_start);
			log_start++;
			spin_unlock_irq(&logbuf_lock);
			error = __put_user(c,buf);
			buf++;
			i++;
			cond_resched();
			spin_lock_irq(&logbuf_lock);
		}
		spin_unlock_irq(&logbuf_lock);
		if (!error)
			error = i;
		break;
	
	...
}

  从以上代码看出,从log_buf中读取数据,直到log_start与log_end相等。若没有新的内容,则cat /proc/kmsg为空。

5 程序

  功能:模仿/proc/kmsg,建立自己的/proc/mykmsg。

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/kernel.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <linux/fs.h>

#include <asm/uaccess.h>
#include <asm/io.h>


#include <linux/sched.h>
#include <linux/module.h>

#define MYLOG_BUF_LEN 1024

struct proc_dir_entry *myentry;

static char mylog_buf[MYLOG_BUF_LEN];
static char tmp_buf[MYLOG_BUF_LEN];
static int mylog_r = 0;
static int mylog_r_for_read = 0;
static int mylog_w = 0;

static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq);

static int is_mylog_empty(void)
{
	return (mylog_r == mylog_w);
}

static int is_mylog_empty_for_read(void)
{
	return (mylog_r_for_read == mylog_w);
}

static int is_mylog_full(void)
{
	return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r);
}

static void mylog_putc(char c)
{
	if (is_mylog_full())
	{
		/* 丢弃一个数据 */
		mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;

		if ((mylog_r_for_read + 1) % MYLOG_BUF_LEN == mylog_r)
		{
			mylog_r_for_read = mylog_r;
		}
	}

	mylog_buf[mylog_w] = c;
	mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN;

	/* 唤醒等待数据的进程 */	
    wake_up_interruptible(&mymsg_waitq);   /* 唤醒休眠的进程 */	
}

static int mylog_getc(char *p)
{
	if (is_mylog_empty())
	{
		return 0;
	}
	*p = mylog_buf[mylog_r];
	mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN;
	return 1;
}

static int mylog_getc_for_read(char *p)
{
	if (is_mylog_empty_for_read())
	{
		return 0;
	}
	*p = mylog_buf[mylog_r_for_read];
	mylog_r_for_read = (mylog_r_for_read + 1) % MYLOG_BUF_LEN;
	return 1;
}


int myprintk(const char *fmt, ...)
{
	va_list args;
	int i;
	int j;

	va_start(args, fmt);
	i = vsnprintf(tmp_buf, INT_MAX, fmt, args);
	va_end(args);
	
	for (j = 0; j < i; j++)
		mylog_putc(tmp_buf[j]);
		
	return i;
}

static ssize_t mymsg_read(struct file *file, char __user *buf,
			 size_t count, loff_t *ppos)
{
	int error = 0;
	int i = 0;
	char c;

	/* 把mylog_buf的数据copy_to_user, return */
	if ((file->f_flags & O_NONBLOCK) && is_mylog_empty_for_read())
		return -EAGAIN;

	//printk("%s %d\n", __FUNCTION__, __LINE__);
	//printk("count = %d\n", count);
	//printk("mylog_r = %d\n", mylog_r);
	//printk("mylog_w = %d\n", mylog_w);

	error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty_for_read());

	//printk("%s %d\n", __FUNCTION__, __LINE__);
	//printk("count = %d\n", count);
	//printk("mylog_r = %d\n", mylog_r);
	//printk("mylog_w = %d\n", mylog_w);

	/* copy_to_user */
	while (!error && (mylog_getc_for_read(&c)) && i < count) {
		error = __put_user(c, buf);
		buf++;
		i++;
	}
	
	if (!error)
		error = i;
	
	return error;
}

static int mymsg_open(struct inode *inode, struct file *file)
{
	mylog_r_for_read = mylog_r;
	return 0;
}

const struct file_operations proc_mymsg_operations = {
	.open = mymsg_open,
	.read = mymsg_read,
};

static int mymsg_init(void)
{	
	proc_create("mymsg", S_IRUSR, NULL, &proc_mymsg_operations);
	return 0;
}

static void mymsg_exit(void)
{
	remove_proc_entry("mymsg", NULL);
}

module_init(mymsg_init);
module_exit(mymsg_exit);

MODULE_LICENSE("GPL");

EXPORT_SYMBOL(myprintk);

6 测试

  加载模块mymsg.ko,
  找一个测试驱动,引入myprintk,并加入打印。

extern int myprintk(const char *fmt, ...);

  加载测试模块。
  查看cat /proc/mykmsg。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值