一直都不清楚是怎么被定位到串口的,所以也非常想搞明白,因为以后可能把标准输入输出还原到键盘和显示器上去,所以决心自己再读一读源码了。
不过内核用的打印函数printk完全是和stdin或stdout无关的,因为一开始到start_kernel函数刚开始进入内核就可以用printk函数了,而建立stdin和stdout是在init函数中实现的。有个问题,在我这里的代码中,建立stdin和stdout如下
if (open("/dev/null", O_RDWR, 0) < 0)
printk("Warning: unable to open an initial console./n");
(void) dup(0);
(void) dup(0);
问题在于它打开的是/dev/null,而一般pc机上的linux打开的都是/dev/console,而且我把这几行代码删除也没有问题,所以我猜想这里建立stdin和stdout并没什么用,肯定在shell中建立了定位到串口的stdin和stdout。所以接下来还需要看看busybox的代码吧。
在这里还是主要分析一下printk实现的原理。
static spinlock_t logbuf_lock = SPIN_LOCK_UNLOCKED; //定义logbuf_lock,并初始化为unlock状态
static char log_buf[LOG_BUF_LEN]; //保存日志数据的缓冲区
#define LOG_BUF(idx) (log_buf[(idx) & LOG_BUF_MASK])
static DECLARE_MUTEX(console_sem); //定义全局互斥信号量console_sem并初始化为1
asmlinkage int printk(const char *fmt, ...)
{
va_list args;
unsigned long flags;
int printed_len;
char *p;
static char printk_buf[1024];
static int log_level_unknown = 1;
if (oops_in_progress) // default : oops_in_progress = 0
{ //oops_in_progress指示进程发生错误,只有在panic()函数中才等于1
//所以一般情况下下两句都不运行
/* If a crash is occurring, make sure we can't deadlock */
spin_lock_init(&logbuf_lock); //初始化logbuf_lock
/* And make sure that we print immediately */
init_MUTEX(&console_sem); //初始化console_sem为互斥的信号量,初值为1
}
/* This stops the holder of console_sem just where we want him */
spin_lock_irqsave(&logbuf_lock, flags);
//一般spin_lock在单cpu中无效的,所以spin_lock_irqsave真正的作用是关中断 和保存状态寄存器。
/* Emit the output into the temporary buffer */
va_start(args, fmt);
printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);
//先把数据格式化到printk_buf中去
va_end(args);
/*
* Copy the output into log_buf. If the caller didn't provide
* appropriate log level tags, we insert them here
*/
//emit_log_char 把字符存入log_buf中等待被发送,具体的参见下面的分析
for (p = printk_buf; *p; p++) {
if (log_level_unknown) {
if (p[0] != '<' || p[1] < '0' || p[1] > '7' || p[2] != '>') {
emit_log_char('<');
emit_log_char(default_message_loglevel + '0');
emit_log_char('>');
}
//如果没有提供<1>类似的日志级别,则在此加上<4>
//我这里的default_message_loglevel=4
log_level_unknown = 0;
}
emit_log_char(*p);
if (*p == '/n') //每一行前面都需要加<4>之类的日志级别
log_level_unknown = 1;
}
if (!arch_consoles_callable()) // unexecute
{
/* 控制台是否可调用,一般下面的不会被执行
* On some architectures, the consoles are not usable
* on secondary CPUs early in the boot process.
*/
spin_unlock_irqrestore(&logbuf_lock, flags);
goto out;
}
if (!down_trylock(&console_sem)) //lock ok
{
/* down_trylock获取信号量,lock则返回0,否则立即返回非0值
* We own the drivers. We can drop the spinlock and let
* release_console_sem() print the text
*/
spin_unlock_irqrestore(&logbuf_lock, flags);
console_may_schedule = 0;
release_console_sem(); //在这个函数中把数据发送到串口并释放console_sem
} else {
/*
* Someone else owns the drivers. We drop the spinlock, which
* allows the semaphore holder to proceed and to call the
* console drivers with the output which we just produced.
*/
spin_unlock_irqrestore(&logbuf_lock, flags);
}
out:
return printed_len;
}
static unsigned long log_start; /* Index into log_buf: next char to be read by syslog() */
static unsigned long con_start; /* Index into log_buf: next char to be sent to consoles */
static unsigned long log_end; /* Index into log_buf: most-recently-written-char + 1 */
static unsigned long logged_chars; /* Number of chars produced since last read+clear operation */
static void emit_log_char(char c)
{
LOG_BUF(log_end) = c; //把字符c存到log_buf缓冲区中,缓冲区满了就会覆盖开始的数据
log_end++; //
if (log_end - log_start > LOG_BUF_LEN) //log_start指示syslog读取的开始
log_start = log_end - LOG_BUF_LEN;//缓冲区满了会把开始的指针向前推
if (log_end - con_start > LOG_BUF_LEN) //con_start指示控制台读取的开始
con_start = log_end - LOG_BUF_LEN;
if (logged_chars < LOG_BUF_LEN)
logged_chars++;
}
void release_console_sem(void)
{
unsigned long flags;
unsigned long _con_start, _log_end;
unsigned long must_wake_klogd = 0;
for ( ; ; ) {
spin_lock_irqsave(&logbuf_lock, flags);//关中断和保存flag
must_wake_klogd |= log_start - log_end; //唤醒klogd标志
if (con_start == log_end)
break; /* Nothing to print */
_con_start = con_start;
_log_end = log_end;
con_start = log_end; /* Flush , con_start向前移用了,可见缓冲区是循环使用的 */
spin_unlock_irqrestore(&logbuf_lock, flags);
call_console_drivers(_con_start, _log_end);//在这个函数中发送数据,见下面的分析
}
console_may_schedule = 0; //指示数据发送时是否能进行任务调度,在使用串口控制台时没用
up(&console_sem); //释放信号量
spin_unlock_irqrestore(&logbuf_lock, flags);
if (must_wake_klogd && !oops_in_progress)
wake_up_interruptible(&log_wait);
}
static void call_console_drivers(unsigned long start, unsigned long end)
{
unsigned long cur_index, start_print;
static int msg_level = -1;
if (((long)(start - end)) > 0)
BUG();
cur_index = start;
start_print = start;
while (cur_index != end) {
if ( msg_level < 0 &&
((end - cur_index) > 2) &&
LOG_BUF(cur_index + 0) == '<' &&
LOG_BUF(cur_index + 1) >= '0' &&
LOG_BUF(cur_index + 1) <= '7' &&
LOG_BUF(cur_index + 2) == '>')
{
msg_level = LOG_BUF(cur_index + 1) - '0';
cur_index += 3;
start_print = cur_index;
} //去除每行开头的类似<4>的日志级别,把它赋给msg_level
while (cur_index != end) {
char c = LOG_BUF(cur_index);
cur_index++;
if (c == '/n') {
if (msg_level < 0) {
/*
* printk() has already given us loglevel tags in
* the buffer. This code is here in case the
* log buffer has wrapped right round and scribbled
* on those tags
*/
msg_level = default_message_loglevel;
}
_call_console_drivers(start_print, cur_index, msg_level);
//发送一行数据
msg_level = -1;
start_print = cur_index;
break;
}
}
}
_call_console_drivers(start_print, end, msg_level); //发送剩余的数据
}
struct console *console_drivers; //全局的console类型的结构体
static void _call_console_drivers(unsigned long start, unsigned long end, int msg_log_level)
{
//如果msg_log_level < console_loglevel 并且 console_drivers存在 并且 start != end
if (msg_log_level < console_loglevel && console_drivers && start != end) {
if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {
/* wrapped write */
//缓冲区循环使用就会出现(start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)
//的清况,于是就分两部分来发送.
//__call_console_drivers才是真正的发送函数
__call_console_drivers(start & LOG_BUF_MASK, LOG_BUF_LEN);
__call_console_drivers(0, end & LOG_BUF_MASK);
} else {
__call_console_drivers(start, end);
}
}
}
static void __call_console_drivers(unsigned long start, unsigned long end)
{
struct console *con;
for (con = console_drivers; con; con = con->next) {
if ((con->flags & CON_ENABLED) && con->write)
con->write(con, &LOG_BUF(start), end - start);
} //调用console_drivers->write来把数据发送出去
}
接下来理解一下console_drivers这个结构体指针
struct console
{
char name[8];
void (*write)(struct console *, const char *, unsigned);
int (*read)(struct console *, const char *, unsigned);
kdev_t (*device)(struct console *);
int (*wait_key)(struct console *);
void (*unblank)(void);
int (*setup)(struct console *, char *);
short flags;
short index;
int cflag;
struct console *next;
};
而开始console_drivers这个指针是NULL的,在什么时候被赋值的呢,原本以为应该在用printk以前就被初始化了,其实不然,它是在start_kernel函数中调用的console_init()函数中被初始化的.最好的办法还是看看代码.
void __init console_init(void)
{
/* Setup the default TTY line discipline. */
memset(ldiscs, 0, sizeof(ldiscs));
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
/*
* Set up the standard termios. Individual tty drivers may
* deviate from this; this is used as a template.
*/
memset(&tty_std_termios, 0, sizeof(struct termios));
memcpy(tty_std_termios.c_cc, INIT_C_CC, NCCS);
tty_std_termios.c_iflag = ICRNL | IXON;
tty_std_termios.c_oflag = OPOST | ONLCR;
#ifdef CONFIG_MIZI //CONFIG_MIZI=1
tty_std_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL;
#else
tty_std_termios.c_cflag = B38400 | CS8 | CREAD | HUPCL;
#endif
tty_std_termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
ECHOCTL | ECHOKE | IEXTEN;
/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
#ifdef CONFIG_VT //如果不是串口控制台就定义这个虚拟控制台
con_init(); //于是输入是键盘输出是显示器
#endif
#ifdef CONFIG_AU1000_SERIAL_CONSOLE
au1000_serial_console_init();
#endif
#ifdef CONFIG_SERIAL_CONSOLE
#if (defined(CONFIG_8xx) || defined(CONFIG_8260))
console_8xx_init();
#elif defined(CONFIG_MAC_SERIAL) && defined(CONFIG_SERIAL)
if (_machine == _MACH_Pmac)
mac_scc_console_init();
else
serial_console_init();
#elif defined(CONFIG_MAC_SERIAL)
mac_scc_console_init();
#elif defined(CONFIG_PARISC)
pdc_console_init();
#elif defined(CONFIG_SERIAL)
serial_console_init();
#endif /* CONFIG_8xx */
#ifdef CONFIG_SGI_SERIAL
sgi_serial_console_init();
#endif
#if defined(CONFIG_MVME162_SCC) || defined(CONFIG_BVME6000_SCC) || defined(CONFIG_MVME147_SCC)
vme_scc_console_init();
#endif
#if defined(CONFIG_SERIAL167)
serial167_console_init();
#endif
#if defined(CONFIG_SH_SCI)
sci_console_init();
#endif
#endif
#ifdef CONFIG_TN3270_CONSOLE
tub3270_con_init();
#endif
#ifdef CONFIG_TN3215
con3215_init();
#endif
#ifdef CONFIG_HWC
hwc_console_init();
#endif
#ifdef CONFIG_STDIO_CONSOLE
stdio_console_init();
#endif
#ifdef CONFIG_SERIAL_CORE_CONSOLE // CONFIG_SERIAL_CORE_CONSOLE=1
uart_console_init(); //这里唯一一个被运行的函数
#endif
#ifdef CONFIG_ARC_CONSOLE
arc_console_init();
#endif
#ifdef CONFIG_SERIAL_TX3912_CONSOLE
tx3912_console_init();
#endif
}
void __init uart_console_init(void)
{
#ifdef CONFIG_SERIAL_AMBA_CONSOLE
ambauart_console_init();
#endif
#ifdef CONFIG_SERIAL_ANAKIN_CONSOLE
anakin_console_init();
#endif
#ifdef CONFIG_SERIAL_CLPS711X_CONSOLE
clps711xuart_console_init();
#endif
#ifdef CONFIG_SERIAL_21285_CONSOLE
rs285_console_init();
#endif
#ifdef CONFIG_SERIAL_SA1100_CONSOLE
sa1100_rs_console_init();
#endif
#ifdef CONFIG_SERIAL_8250_CONSOLE
serial8250_console_init();
#endif
#ifdef CONFIG_SERIAL_UART00_CONSOLE
uart00_console_init();
#endif
#ifdef CONFIG_SERIAL_S3C2400_CONSOLE
s3c2400_console_init();
#endif
#ifdef CONFIG_SERIAL_S3C2410_CONSOLE
s3c2410_console_init(); //这个函数被运行
#endif
}
void __init s3c2410_console_init(void)
{
register_console(&s3c2410_cons); //调用注册控制台的函数
}
static struct console s3c2410_cons = {
name: "ttyS",
write: s3c2410_console_write,
device: s3c2410_console_device,
wait_key: s3c2410_console_wait_key,
setup: s3c2410_console_setup,
flags: CON_PRINTBUFFER,
index: -1,
}; //这个就是console_drivers所指向的结构了
void register_console(struct console * console)
{ //该函数就是把console_drivers这个全局指针指向console结构体了,而且在注册完后
//会把存在缓冲区中的都发送出去,所以在注册console以前调用的printk并不发送数据
//而只是把数据存到缓冲区里,注册了以后才能被马上发送.多个控制台被注册的话就会
//形成一个链表结构,都能发送数据.
int i;
unsigned long flags;
/*
* See if we want to use this console driver. If we
* didn't select a console we take the first one
* that registers here.
*/
if (preferred_console < 0) {
if (console->index < 0)
console->index = 0;
if (console->setup == NULL ||
console->setup(console, NULL) == 0) {
console->flags |= CON_ENABLED | CON_CONSDEV;
preferred_console = 0;
}
}
/*
* See if this console matches one we selected on
* the command line.
*/
for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) {
if (strcmp(console_cmdline[i].name, console->name) != 0)
continue;
if (console->index >= 0 &&
console->index != console_cmdline[i].index)
continue;
if (console->index < 0)
console->index = console_cmdline[i].index;
if (console->setup &&
console->setup(console, console_cmdline[i].options) != 0)
break;
console->flags |= CON_ENABLED;
console->index = console_cmdline[i].index;
if (i == preferred_console)
console->flags |= CON_CONSDEV;
break;
}
if (!(console->flags & CON_ENABLED))
return;
/*
* Put this console in the list - keep the
* preferred driver at the head of the list.
*/
acquire_console_sem();
if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
console->next = console_drivers;
console_drivers = console;
} else {
console->next = console_drivers->next;
console_drivers->next = console;
}
if (console->flags & CON_PRINTBUFFER) {
/*
* release_cosole_sem() will print out the buffered messages for us.
*/
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_start;
spin_unlock_irqrestore(&logbuf_lock, flags);
}
release_console_sem();
}