编写自己的内核打印函数

原创文章,转载写明出处

作者Crazii @ 优快云

我看了minix的内核printf函数之后,发现它里面的goto有语句...我就读不下去了,于是自己写了一个.kprintf

首先要了解的就是printf的变参.这得从c调用规则讲起.回顾一下c(cdecl)调用规则(vc里面__cdecl关键字,MASM里面的.model c)除了命名要加下划线之外,还有函数的调用:参数是从右至左入栈,并且是调用者恢复栈.

;printf(fmt,s);假定fmt和s是指针变量

push s

push fmt

call _printf

add esp 8

嗯,这里还有一个问题,就是栈对齐问题.如果是char型参数,或者是short型参数,那么压栈的数据宽度是多少?16位下我们知道,最小得是一个word,也就是short型,char型数据入栈时,也是按word.而32位下,压栈的默认大小跟段描述符里面的描述有关,如果段是16位段,则按16位入栈,如果是32位段,则按32位入栈.这里当然要说32位的栈了,push 0xFF,这样的立即数是按32位对齐,push ds这样的16位段寄存器也是按32位对齐.好了.ok到这里,感觉离题有点远了..

 

正因为cdecl调用跟stdcall不同,它是调用者负责恢复栈的,所以调用者想压几个参数就压几个参数,调用完了自己在平衡堆栈就OK了,所以可以支持变参,但是stdcall是被调用函数在结束时ret n来维护栈的,调用者不能随便压参数.总之,cdecl支持可变参数vararg,所以才有现在的printf

嗯,根据C调用规则,就知道了栈上的参数分布,这样在读取可变参数时,就好办了.

  1. /******************************************************
  2. stdarg.h
  3. standard argument header for NGOS
  4. *******************************************************
  5. C语言参数头文件
  6. Author: ZZU-Crazii
  7. */
  8. typedef char    *var_list;  //VC里面也是这样写的,跟我想的一样
  9. /*  参考VC里的头文件,栈的对齐,是sizeof(int)的整数倍  */
  10. #define INT_SIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
  11. /* 按对齐,取下一个参数 */
  12. #define var_start(arg_ptr,arg_list) (  (void) (arg_ptr = (var_list)(&arg_list)+INT_SIZEOF(arg_list)) )
  13. /* 将取出的参数强制转换为指定的Type,并指向下一个参数 */
  14. #define var_arg(arg_ptr,type)   (*(type*)   ( (arg_ptr +=INT_SIZEOF(type))-INT_SIZEOF(type) )   )
  15. #define var_end(arg_ptr) ((void)(arg_ptr=NULL)

 

需要说明的是,我自己写的stdarg之前的版本,栈取参数后的增量是++操作(+sizeof(type)),那么如果取出的类型是char,则varptr是+1的,这显然不符合栈对齐原则.应该将sizeof(type)对齐到INT长度的倍数,就像VC的crt头文件里面一样.这样,如果取出的参数是char,那么varptr将+4,来指向下一个参数好了,可以看kprintf了:

  1. //这个宏来自minix
  2. #define isdigit(c)  ( (unsigned)( (unsigned)(c) - '0') <= (unsigned)9 )
  3. #define BUFFER_SIZE 32  /* 将数字转化为字符串的缓冲 */
  4. /*****************kprintf*************************/
  5. #include <stdarg.h> /* 自己做的头文件 */
  6. /* 支持%s, %d,%u 的简单版*/
  7. /* 3.26,添加%x,长度,对齐.等 */
  8. /* 3.26 稍作修改,添加%X */
  9. void kprintf(const char *fmt,...)
  10. {
  11.     var_list    argp;
  12.     int c;
  13.     /*const char *pString;*/
  14.     const char *Ostring;
  15.     union
  16.     {
  17.         int     s;  /* 有符号 */
  18.         unsigned    uns;    /* 无符号 */
  19.     }num;
  20.     int i;
  21.     unsigned int len;   
  22.     static char tmp[BUFFER_SIZE];
  23.     static const char null_pointer[] ="(NULL)";
  24.     
  25.     int width;      /* 指定显示长度 */
  26.     unsigned int limit; /* 截断显示长度 */
  27.     int align_left;
  28.     char    fill_prefix;
  29.     
  30.     if( fmt == NULL ) return;
  31.     var_start(argp,fmt);
  32.     
  33.     while( (c=*(fmt++)) !=NULL )
  34.     {
  35.         if( c!='%' )
  36.         {
  37.             kputchar(c);
  38.             continue;
  39.         }
  40.         num.uns = 0;
  41.         Ostring = NULL;
  42.         
  43.         c = *(fmt++);       /* 跳过 '%' */
  44.         
  45.         /* 对齐方式 */
  46.         align_left = FALSE;
  47.         if( c == '-' )
  48.         {
  49.             align_left = TRUE;
  50.             c = *(fmt++);
  51.         }
  52.         
  53.         /* '0'前缀 */
  54.         fill_prefix =' ';
  55.         if( c == '0')
  56.         {
  57.             fill_prefix = '0';
  58.             c = *(fmt++);
  59.         }
  60.         
  61.         /* 宽度 */
  62.         width = 0;
  63.         if( c == '*' )
  64.         {
  65.             width = var_arg(argp,int);
  66.             c = *(fmt++);
  67.         }
  68.         else while ( isdigit(c) )
  69.         {
  70.             width= width * 10 + (c - '0');
  71.             c = *(fmt++);
  72.         }
  73.         
  74.         /* 截断 */ /* 修复BUG,limit=0,串不能显示 */
  75.         limit = ULONG_MAX;
  76.         if( c == '.' )
  77.         {
  78.             c = *(fmt++);
  79.             if( isdigit(c) )
  80.             {
  81.                 limit = 0;
  82.                 while( isdigit(c) )
  83.                 {
  84.                     limit= limit * 10 + (c - '0');
  85.                     c = *(fmt++);
  86.                 }
  87.             }
  88.         }
  89.         
  90.         switch(c)
  91.         {
  92.             case 's':   /* %s */
  93.                 Ostring=var_arg(argp,char*);
  94.                 if( Ostring == NULL )
  95.                     Ostring=null_pointer;
  96.                 
  97.                 /* 计算字符串长度 */
  98.                 for( i=0; Ostring[i]!=0; i++);
  99.                 len = i;
  100.                 break;
  101.             case 'd':   /* %d */
  102.                 num.s=var_arg(argp,int);
  103.                 
  104.                 if(num.s==0)
  105.                 {
  106.                     tmp[0] ='0';
  107.                     Ostring = tmp;
  108.                     len =1;
  109.                     break;
  110.                 }
  111.                 if( num.s<0 )       /* 将负数转换成正数 */
  112.                 {
  113.                     kputchar('-');
  114.                     num.s = -num.s;
  115.                 }
  116.                 /* !!!没有break,继续,作为无符号数处理 */
  117.             case 'u':   /* %u */
  118.                 if( num.uns == 0 ) num.uns = var_arg(argp,unsigned int);
  119.                 if(num.uns==0)
  120.                 {
  121.                     tmp[0] ='0';
  122.                     Ostring = tmp;
  123.                     len =1;
  124.                     break;
  125.                 }
  126.                 /* 3.26 改成从高字节(BUFFER_SIZE-1)开始写入字符 */
  127.                 for(i=0; num.uns/10 !=0 || num.uns%10 !=0;num.uns/=10,i++)
  128.                     tmp[BUFFER_SIZE-1-i] = (char)(num.uns%10) + '0';
  129.                 
  130.                 /****************************
  131.                 字符串: H=结束字节,第一个写入,
  132.                     L=开始字节,最后一个被写入,
  133.                     B=缓冲区开始位置
  134.                     |<-长度i->|
  135.                 |B|.....|L|.....|H|
  136.                      ^
  137.                      |
  138.                     (BUFFER_SIZE-i)索引的最终位置
  139.                 *****************************/
  140.                 Ostring = tmp+ (BUFFER_SIZE - i);
  141.                 len = i;
  142.                 /* 数字不做截断处理 */
  143.                 limit = ULONG_MAX;
  144.                 break;
  145.             case 'x'case'X':
  146.                 num.uns = var_arg(argp,unsigned int);
  147.                 if(num.uns==0)
  148.                 {
  149.                     tmp[0] ='0';
  150.                     Ostring = tmp;
  151.                     len =1;
  152.                     break;
  153.                 }
  154.                 for(i=0; num.uns/16 !=0 || num.uns%16 !=0;num.uns/=16,i++)
  155.                 {
  156.                     if( (num.uns%16) < 10 )
  157.                         tmp[BUFFER_SIZE-1-i] = (char)(num.uns%16) + '0';
  158.                     else        /* 'x'-'a' == 'X'-'a' 嘎嘎~ */
  159.                         tmp[BUFFER_SIZE-1-i] = (char)((num.uns%16)-10)+c-('x'-'a');
  160.                 }
  161.                 Ostring = tmp+ (BUFFER_SIZE - i);
  162.                 len = i;
  163.                 /* 数字不做截断处理 */
  164.                 limit = ULONG_MAX;
  165.                 break;
  166.             default:
  167.                 kputchar('%');
  168.                 kputchar(c);
  169.                 break;
  170.         }
  171.         /* 问题:数字缓冲是倒序,但是字符串是正序排列... */
  172.         /* 把数字缓冲改成正序.OK */
  173.         if( Ostring != NULL )
  174.         {
  175.             /* 计算对齐并输出 */
  176.             if( !align_left && len< width )
  177.             {
  178.                 do
  179.                 {
  180.                     kputchar(fill_prefix);
  181.                     width--;
  182.                 }while( len<width );
  183.             }
  184.             
  185.             /* 截断处理 */
  186.             if( len > limit )
  187.                 len = limit;
  188.                 
  189.             for(i=0;i<len;i++)
  190.                 kputchar( Ostring[i] );
  191.                 
  192.             if( align_left && len< width )
  193.             {
  194.                 do
  195.                 {
  196.                     kputchar(' ');
  197.                     width--;
  198.                 }while( len<width );
  199.             }
  200.         }
  201.     }
  202.     var_end(argp);
  203.     kputchar( NULL );        /* 释放缓冲,显示 */        
  204. }


 

### 实现串口打印函数 `printk` 的方法 在嵌入式系统或操作系统内核中,`printk` 是一个非常重要的调试工具,它允许开发者将调试信息输出到串口控制台。实现 `printk` 的核心在于将内核的打印机制与具体的串口硬件驱动进行绑定。 #### 1. 理解 `printk` 的基本机制 `printk` 是 Linux 内核中的打印函数,其工作原理类似于标准 C 库中的 `printf`,但它不依赖于用户空间的库函数,而是直接在内核空间中运行。`printk` 会将信息存储在内核的日志缓冲区中,然后通过注册的控制台设备(如串口)进行输出[^1]。 `printk` 的输出行为受到日志级别(loglevel)的控制,只有当日志级别高于或等于控制台的当前日志级别时,信息才会被输出。例如,默认的日志级别是 4(KERN_WARNING),而控制台的日志级别通常为 7(KERN_DEBUG),这意味着默认情况下所有级别的信息都会被输出[^3]。 #### 2. 初始化串口控制台 在嵌入式系统中,串口通常被用作控制台设备。为了使 `printk` 能够通过串口输出信息,必须在内核启动阶段完成以下步骤: - **硬件初始化**:在内核启动的早期阶段(通常是 C 语言部分的 `start_kernel` 函数中),需要完成串口硬件的初始化。这包括设置波特率、数据位、停止位和奇偶校验等参数[^4]。 - **注册控制台设备**:使用 `register_console()` 函数将串口设备注册为控制台设备。这个函数会将一个 `struct console` 结构体添加到内核的控制台列表中,该结构体包含了一个 `write` 函数指针,用于实际的字符输出操作。 ```c static struct console serial_console = { .name = "ttyS", .write = serial_console_write, .device = NULL, .flags = CON_PRINTBUFFER, .index = -1, }; static int __init serial_console_init(void) { register_console(&serial_console); return 0; } console_initcall(serial_console_init); ``` 上述代码定义了一个 `serial_console` 结构体,并在 `serial_console_init` 函数中通过 `register_console()` 将其注册为控制台设备。`serial_console_write` 是实际用于输出字符的函数。 #### 3. 实现串口输出函数 `serial_console_write` 函数负责将字符发送到串口设备。该函数通常会直接操作串口寄存器,以确保字符能够被正确地发送出去。以下是一个简单的实现示例: ```c static void serial_console_write(struct console *co, const char *s, unsigned int count) { while (count--) { if (*s == &#39;\n&#39;) serial_putc(&#39;\r&#39;); // 添加回车符 serial_putc(*s++); } } static void serial_putc(char c) { // 假设串口寄存器地址为 UART0_BASE while ((readl(UART0_BASE + UART_LSR) & UART_LSR_THRE) == 0) ; // 等待发送缓冲区为空 writel(c, UART0_BASE + UART_TX); } ``` 在这个例子中,`serial_putc` 函数用于将单个字符写入串口寄存器。它首先等待发送缓冲区变为空(通过检查 `UART_LSR_THRE` 位),然后将字符写入发送寄存器。`serial_console_write` 则负责处理字符串中的每个字符,并在遇到换行符时添加回车符。 #### 4. 调整日志级别 如果发现 `printk` 的信息没有被正确输出,可以检查并调整内核的日志级别。可以通过修改 `/proc/sys/kernel/printk` 文件来设置日志级别。该文件包含四个整数,分别表示控制台日志级别、默认消息日志级别、最小(最高优先级)日志级别和默认控制台日志级别。 ```bash echo 7 > /proc/sys/kernel/printk ``` 此命令将控制台日志级别设置为 7(KERN_DEBUG),确保所有级别的 `printk` 信息都会被输出[^3]。 #### 5. 调试与验证 在实现完 `printk` 和串口控制台之后,可以通过调用 `printk` 函数来输出调试信息,并观察串口终端是否能够正确显示这些信息。例如: ```c printk(KERN_INFO "Serial console initialized successfully.\n"); ``` 如果一切正常,这段代码会在串口终端上输出一条提示信息。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值