printk实现原理
printk->tty_write->con_write
printk格式化输出
printk的函数,用于在控制台上输出格式化的字符串。
该函数使用了可变参数列表,可以接受任意数量的参数。
首先,使用va_start宏初始化一个va_list类型的变量args,然后使用vsprintf函数将格式化的字符串和可变参数列表args打印到一个缓冲区buf中,返回打印的字符数。
首先将fs寄存器和ds寄存器的值压入栈中,然后将fs寄存器的值设置为ds寄存器的值,这是为了在访问buf时使用fs寄存器,因为fs寄存器通常用于指向当前进程的TSS(任务状态段),而TSS中包含了当前进程的内核栈。然后将打印的字符数i压入栈中,接着将buf的地址和一个值为0的参数依次压入栈中,调用tty_write函数将buf中的内容写入控制台。最后,将栈中的值弹出,恢复fs寄存器和ds寄存器的值,返回打印的字符数i。
static char buf[1024];extern int vsprintf(char * buf, const char * fmt, va_list args);int printk(const char *fmt, ...)
{va_list args;int i;va_start(args, fmt);i=vsprintf(buf,fmt,args);va_end(args);__asm__("push %%fs\n\t""push %%ds\n\t""pop %%fs\n\t""pushl %0\n\t""pushl $buf\n\t""pushl $0\n\t""call tty_write\n\t""addl $8,%%esp\n\t""popl %0\n\t""pop %%fs"::"r" (i):"ax","cx","dx");return i;
}
tty_write终端输出函数
tty_write函数,用于将字符数组buf中的内容写入到指定通道channel对应的终端设备中。函数的返回值是成功写入的字符数。
函数首先检查通道号是否大于2或者写入字符数nr是否小于0,如果是,则返回-1表示错误。
接下来,函数通过将通道号channel与tty_table相加,得到对应的tty_struct结构体指针tty。
然后,函数进入一个循环,直到写入字符数nr为0。在每次循环中,函数会调用sleep_if_full函数,如果写入队列tty->write_q已满,则会使当前进程进入睡眠状态,直到队列有足够的空间。
接着,函数检查当前进程是否有信号待处理,如果有,则跳出循环。
然后,函数进入一个嵌套循环,直到写入字符数nr为0或者写入队列tty->write_q已满。在每次循环中,函数从buf中读取一个字符c,并根据终端设备的配置进行处理。处理包括:将回车符'\r'转换为换行符'\n'(如果配置允许),将换行符'\n'转换为回车符'\r'(如果配置允许),将换行符'\n'转换为回车符'\r'并插入队列中(如果配置允许),将字符转换为大写(如果配置允许)。然后,指针b向后移动一位,写入字符数nr减1,重置cr_flag为0,并将字符c插入到写入队列tty->write_q中。
接着,函数调用tty->write函数,将写入队列中的字符进行实际的写入操作。
如果写入字符数nr仍大于0,则调用schedule函数进行进程调度,让其他进程有机会执行。
int tty_write(unsigned channel, char * buf, int nr)
{static int cr_flag=0;struct tty_struct * tty;char c, *b=buf;if (channel>2 || nr<0) return -1;tty = channel + tty_table;while (nr>0) {sleep_if_full(&tty->write_q);if (current->signal)break;while (nr>0 && !FULL(tty->write_q)) {c=get_fs_byte(b);if (O_POST(tty)) {if (c=='\r' && O_CRNL(tty))c='\n';else if (c=='\n' && O_NLRET(tty))c='\r';if (c=='\n' && !cr_flag && O_NLCR(tty)) {cr_flag = 1;PUTCH(13,tty->write_q);continue;}if (O_LCUC(tty))c=toupper(c);}b++; nr--;cr_flag = 0;PUTCH(c,tty->write_q);}tty->write(tty);if (nr>0)schedule();}return (b-buf);
}
con_write将字符写入终端
函数内部定义了一些变量,包括nr和c,分别表示写入队列中字符的数量和当前字符。
接下来使用while循环,循环次数为写入队列中字符的数量。
在循环中,使用GETCH宏从写入队列中获取一个字符,并使用switch语句根据字符的不同进行不同的操作。
在case 0中,如果字符的ASCII码在31和127之间,表示是可打印字符,会进行一系列的操作,包括判断是否需要换行、设置字符属性、更新光标位置等。
在case 1中,如果字符是'[',表示后面是控制序列,会进入case 2进行处理。
在case 2中,会解析控制序列中的参数,并根据参数执行相应的操作,比如移动光标、清除屏幕等。
最后,调用set_cursor函数设置光标位置。
void con_write(struct tty_struct * tty)
{int nr;char c;nr = CHARS(tty->write_q);while (nr--) {GETCH(tty->write_q,c);switch(state) {case 0:if (c>31 && c<127) {if (x>=video_num_columns) {x -= video_num_columns;pos -= video_size_row;lf();}__asm__("movb attr,%%ah\n\t""movw %%ax,%1\n\t"::"a" (c),"m" (*(short *)pos));pos += 2;x++;} else if (c==27)state=1;else if (c==10 || c==11 || c==12)lf();else if (c==13)cr();else if (c==ERASE_CHAR(tty))del();else if (c==8) {if (x) {x--;pos -= 2;}} else if (c==9) {c=8-(x&7);x += c;pos += c<<1;if (x>video_num_columns) {x -= video_num_columns;pos -= video_size_row;lf();}c=9;} else if (c==7)sysbeep();break;case 1:state=0;if (c=='[')state=2;else if (c=='E')gotoxy(0,y+1);else if (c=='M')ri();else if (c=='D')lf();else if (c=='Z')respond(tty);else if (x=='7')save_cur();else if (x=='8')restore_cur();break;case 2:for(npar=0;npar<NPAR;npar++)par[npar]=0;npar=0;state=3;if ((ques=(c=='?')))break;case 3:if (c==';' && npar<NPAR-1) {npar++;break;} else if (c>='0' && c<='9') {par[npar]=10*par[npar]+c-'0';break;} else state=4;case 4:state=0;switch(c) {case 'G': case '`':if (par[0]) par[0]--;gotoxy(par[0],y);break;case 'A':if (!par[0]) par[0]++;gotoxy(x,y-par[0]);break;case 'B': case 'e':if (!par[0]) par[0]++;gotoxy(x,y+par[0]);break;case 'C': case 'a':if (!par[0]) par[0]++;gotoxy(x+par[0],y);break;case 'D':if (!par[0]) par[0]++;gotoxy(x-par[0],y);break;case 'E':if (!par[0]) par[0]++;gotoxy(0,y+par[0]);break;case 'F':if (!par[0]) par[0]++;gotoxy(0,y-par[0]);break;case 'd':if (par[0]) par[0]--;gotoxy(x,par[0]);break;case 'H': case 'f':if (par[0]) par[0]--;if (par[1]) par[1]--;gotoxy(par[1],par[0]);break;case 'J':csi_J(par[0]);break;case 'K':csi_K(par[0]);break;case 'L':csi_L(par[0]);break;case 'M':csi_M(par[0]);break;case 'P':csi_P(par[0]);break;case '@':csi_at(par[0]);break;case 'm':csi_m();break;case 'r':if (par[0]) par[0]--;if (!par[1]) par[1] = video_num_lines;if (par[0] < par[1] &&par[1] <= video_num_lines) {top=par[0];bottom=par[1];}break;case 's':save_cur();break;case 'u':restore_cur();break;}}}set_cursor();
}