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。