| 導購 | 订阅 | 在线投稿
分享
 
 
 

內核printf源代碼分析.

來源:互聯網網民  2006-01-09 08:40:20  評論

內核printf源代碼分析.

打開Source Insight來閱讀EduOS的源代碼,我們在stdio.c裏找到了printf的實現代碼.首先看看對printf的定義:

int  printf  (const  char  *cntrl_string , ... )

第一個參數cntrl_string是控制字符串,也就是平常我們寫入%d,%f的地方.緊接著後面是一個變長參數.

看看函數頭部的定義:

int  pos  = 0 , cnt_printed_chars  = 0 , i ;

unsigned  char * chptr ;

va_list  ap ;

馬上暈!除了ap我們可以馬上判斷出來是用來讀取變長參數的,i用于循環變量.其他變量都不知道是怎麽回事.不要著急,我們邊看代碼邊分析.代碼的第一行必然是

va_start  (ap , cntrl_string );

用來初始化變長參數.

接下來是一個while循環

while  (cntrl_string [pos ])

{

    ... 

}

結束條件是cntrl_string[pos]爲NULL,顯然這個循環是用來遍曆整個控制字符串的.自然pos就是當前遍曆到的位置了.進入循環首先闖入視線的是

if  (cntrl_string [pos ] == '%')

{

     pos ++;

     ... 

}

開門見山,上來就當前字符是否辦斷是否%.一猜就知道如果成立pos++馬上取出下一個字符在d,f,l等等之間進行判斷.往下一看,果真不出所料:

switch  (cntrl_string [pos ])

{

     case  'c':

        ...  case  's':

        ...  case  'i':

        ...  case  'd':

        ...  case  'u':

        ... 

用上switch-case了. 快速浏覽一下下面的代碼.

首先看看case 'c'的部分

case  'c':

     putchar  (va_arg  (ap , unsigned  char ));

     cnt_printed_chars ++;

     break ;

%c表示僅僅輸出一個字符.因此先通過va_arg進行參數的類型轉換,之後用putchar[1]輸出到屏幕上去.之後是

cnt_printed_chars++,通過這句我們就可以判斷出cnt_printed_chars使用來表示,已經被printf輸出的字符個數的.

再來看看 case 's':

case  's':

     chptr  = va_arg  (ap , unsigned  char *);

     i  = 0 ;

     while  (chptr  [i ])

    {

         cnt_printed_chars ++;

         putchar  (chptr  [i ++]);

        

} break ;

和case 'c',同出一轍.cnt_printed_chars++放在了循環內,也證明了剛才提到的他的作用.另外我們也看到了cnptr是用來在處理字符串時的位置指針.到此爲止,我們清楚的所有變量的用途,前途變得更加光明了.

接下來:

// PartI

case  'i':

     case  'd':

     cnt_printed_chars  += printInt  (va_arg  (ap , int ));

     break ;

     case  'u':

     cnt_printed_chars  += printUnsignedInt  (va_arg  (ap , unsigned  int ));

     break ;

     case  'x':

     cnt_printed_chars  += printHexa  (va_arg  (ap , unsigned  int ), 'x');

     break ;

     case  'X':

     cnt_printed_chars  += printHexa  (va_arg  (ap , unsigned  int ), 'X');

     break ;

     case  'o':

     cnt_printed_chars  += printOctal  (va_arg  (ap , unsigned  int ));

     break ;

    // Part II

     case  'p':

     putchar  ('0');

     putchar  ('x');

     cnt_printed_chars  += 2 ;

     /* of '0x' */

     cnt_printed_chars  += printHexa  (va_arg  (ap , unsigned  int ), 'x');

     break ;

     case  '#':

     pos ++;

     switch  (cntrl_string [pos ])

    {

         case  'x':

         putchar  ('0');

         putchar  ('x');

         cnt_printed_chars  += 2 ;

         /* of '0x' */

         cnt_printed_chars  += printHexa  (va_arg  (ap , unsigned  int ), 'x');

         break ;

         case  'X':

         putchar  ('0');

         putchar  ('X');

         cnt_printed_chars  += 2 ;

         /* of '0X' */

         cnt_printed_chars  += printHexa  (va_arg  (ap , unsigned  int ), 'X');

         break ;

         case  'o':

         putchar  ('0');

         cnt_printed_chars ++;

         cnt_printed_chars  += printOctal  (va_arg  (ap , unsigned  int ));

         break ;

注意觀察一下,PartII的代碼其實就是比PartI的代碼多一個樣式.在16進制數或八進制前加入0x或是o,等等.因此這裏就只分析一下PartI咯.

其實仔細看看PartI的個條case,也就是把參數分發到了更具體的函數用于顯示,然後以返回值的形式返回輸出個數.對于這些函數就不具體分析了.我們先來看看一些善後處理:

先看case的default處理.

default :

     putchar  ((unsigned  char ) cntrl_string [pos ]);

     cnt_printed_chars ++;

就是直接輸出cntrl_string裏%號後面的未知字符.應該是一種容錯設計處理.

再看看if (cntrl_string[pos] == '%')的else部分

else 

{

     putchar  ((unsigned  char ) cntrl_string [pos ]);

     cnt_printed_chars ++;

     pos ++;

    

}

如果不是%開頭的,那麽直接輸出這個字符.

最後函數返回前

va_end  (ap );

return  cnt_printed_chars ;

va_end處理變長參數的善後工作.並返回輸出的字符個數.

在最後我們有必要談談putChar函數以及基本輸出的基礎函數printChar,先來看看putChar

int  putchar  (int  c )

{

     switch  ((unsigned  char ) c )

    {

         case  'n' :

             newLine  ();

             break ;

             case  'r' :

             carriageReturn  ();

             break ;

             case  'f' :

             clearScreen  ();

             break ;

             case  't' :

             printChar  (32 );

             printChar  (32 );

             /* 32 = space */

             printChar  (32 );

             printChar  (32 );

             printChar  (32 );

             printChar  (32 );

             printChar  (32 );

             printChar  (32 );

             break ;

             case  'b':

             backspace  ();

             break ;

             case  'a':

             beep  ();

             break ;

             default  :

             printChar  ((unsigned  char ) c );

            

    } return  c ;

}

通覽一下,也是switch-case爲主體的.主要是用來應對一些特殊字符,如\n,\r,....這裏需要提一下,關于\t的理解.有些人認爲\t就是8個space,有些人則認爲,屏幕分爲10大列(每個大列8個小列總共80列).一個\t就跳到下一個大列輸出.也就是說不管你現在實在屏幕的第1,2,3,4,5,6,7位置輸出字符,只要一個\t都在第8個位置開始輸出. VS.NET中就是用的這種理解.因此如果按照這個理解的話,\t的實現可以這樣

int  currentX  = ((currentX  % 10 ) + 1 ) * 8 ;

然後在currentX位置輸出.

接下來看printChar也就是輸出部分最低層的操作咯

void  printChar  (const  byte  ch )

{

     *(word  *)(VIDEO  + y  * 160  + x  * 2 ) = ch  | (fill_color  << 8 );

     x ++;

     if  (x  > ;

    = WIDTH ) newLine  ();

     setVideoCursor  (y , x );

}

這裏VIDEO表示顯存地址也就是0xB8000.通過 y * 160 + x 屏幕(x,y)坐標在顯存中的位置.這裏需要知道,一個字符顯示需要兩個字節,一個是ASCII碼,第二個是字符屬性代碼也就是顔色代碼.因此才必須 y * 80 * 2 + x = y * 160 + x.那麽ch | (fill_color << 8)也自然就是寫入字符及屬性代碼用的了.每寫一個字符光標位置加1,如果大于屏幕寬度WIDTH就換行.最後通過setVideoCursor設置新的光標位置.完成了整個printChar過程.

到此,把printf從上到下說了一遍.不知道各位大家感覺如何,如果說得不清楚還大家多提意見.有說得不對的地方請大家多多指教.

 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
內核printf源代碼分析. 打開Source Insight來閱讀EduOS的源代碼,我們在stdio.c裏找到了printf的實現代碼.首先看看對printf的定義: [code] int printf (const char *cntrl_string, ...) [/code] 第一個參數cntrl_string是控制字符串,也就是平常我們寫入%d,%f的地方.緊接著後面是一個變長參數. 看看函數頭部的定義: [code]int pos = 0, cnt_printed_chars = 0, i; unsigned char* chptr; va_list ap;[/code] 馬上暈!除了ap我們可以馬上判斷出來是用來讀取變長參數的,i用于循環變量.其他變量都不知道是怎麽回事.不要著急,我們邊看代碼邊分析.代碼的第一行必然是 [code]va_start (ap, cntrl_string);[/code] 用來初始化變長參數. 接下來是一個while循環 [code]while (cntrl_string[pos]) { ... }[/code] 結束條件是cntrl_string[pos]爲NULL,顯然這個循環是用來遍曆整個控制字符串的.自然pos就是當前遍曆到的位置了.進入循環首先闖入視線的是 [code] if (cntrl_string[pos] == '%') { pos++; ... } [/code] 開門見山,上來就當前字符是否辦斷是否%.一猜就知道如果成立pos++馬上取出下一個字符在d,f,l等等之間進行判斷.往下一看,果真不出所料: [code]switch (cntrl_string[pos]) { case 'c': ... case 's': ... case 'i': ... case 'd': ... case 'u': ...[/code] 用上switch-case了. 快速浏覽一下下面的代碼. 首先看看case 'c'的部分 [code]case 'c': putchar (va_arg (ap, unsigned char)); cnt_printed_chars++; break;[/code] %c表示僅僅輸出一個字符.因此先通過va_arg進行參數的類型轉換,之後用putchar[1]輸出到屏幕上去.之後是 cnt_printed_chars++,通過這句我們就可以判斷出cnt_printed_chars使用來表示,已經被printf輸出的字符個數的. 再來看看 case 's': [code] case 's': chptr = va_arg (ap, unsigned char*); i = 0; while (chptr [i]) { cnt_printed_chars++; putchar (chptr [i++]); } break;[/code]和case 'c',同出一轍.cnt_printed_chars++放在了循環內,也證明了剛才提到的他的作用.另外我們也看到了cnptr是用來在處理字符串時的位置指針.到此爲止,我們清楚的所有變量的用途,前途變得更加光明了. 接下來: [code]// PartI case 'i': case 'd': cnt_printed_chars += printInt (va_arg (ap, int)); break; case 'u': cnt_printed_chars += printUnsignedInt (va_arg (ap, unsigned int)); break; case 'x': cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x'); break; case 'X': cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'X'); break; case 'o': cnt_printed_chars += printOctal (va_arg (ap, unsigned int)); break; // Part II case 'p': putchar ('0'); putchar ('x'); cnt_printed_chars += 2; /* of '0x' */ cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x'); break; case '#': pos++; switch (cntrl_string[pos]) { case 'x': putchar ('0'); putchar ('x'); cnt_printed_chars += 2; /* of '0x' */ cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'x'); break; case 'X': putchar ('0'); putchar ('X'); cnt_printed_chars += 2; /* of '0X' */ cnt_printed_chars += printHexa (va_arg (ap, unsigned int), 'X'); break; case 'o': putchar ('0'); cnt_printed_chars++; cnt_printed_chars += printOctal (va_arg (ap, unsigned int)); break;[/code] 注意觀察一下,PartII的代碼其實就是比PartI的代碼多一個樣式.在16進制數或八進制前加入0x或是o,等等.因此這裏就只分析一下PartI咯. 其實仔細看看PartI的個條case,也就是把參數分發到了更具體的函數用于顯示,然後以返回值的形式返回輸出個數.對于這些函數就不具體分析了.我們先來看看一些善後處理: 先看case的default處理. [code]default: putchar ((unsigned char) cntrl_string[pos]); cnt_printed_chars++;[/code]就是直接輸出cntrl_string裏%號後面的未知字符.應該是一種容錯設計處理. 再看看if (cntrl_string[pos] == '%')的else部分 [code]else { putchar ((unsigned char) cntrl_string[pos]); cnt_printed_chars++; pos++; }[/code] 如果不是%開頭的,那麽直接輸出這個字符. 最後函數返回前 [code]va_end (ap); return cnt_printed_chars;[/code]va_end處理變長參數的善後工作.並返回輸出的字符個數. 在最後我們有必要談談putChar函數以及基本輸出的基礎函數printChar,先來看看putChar [code]int putchar (int c) { switch ((unsigned char) c) { case '\n' : newLine (); break; case '\r' : carriageReturn (); break; case '\f' : clearScreen (); break; case '\t' : printChar (32); printChar (32); /* 32 = space */ printChar (32); printChar (32); printChar (32); printChar (32); printChar (32); printChar (32); break; case '\b': backspace (); break; case '\a': beep (); break; default : printChar ((unsigned char) c); } return c; }[/code] 通覽一下,也是switch-case爲主體的.主要是用來應對一些特殊字符,如\n,\r,....這裏需要提一下,關于\t的理解.有些人認爲\t就是8個space,有些人則認爲,屏幕分爲10大列(每個大列8個小列總共80列).一個\t就跳到下一個大列輸出.也就是說不管你現在實在屏幕的第1,2,3,4,5,6,7位置輸出字符,只要一個\t都在第8個位置開始輸出. VS.NET中就是用的這種理解.因此如果按照這個理解的話,\t的實現可以這樣 [code]int currentX = ((currentX % 10) + 1) * 8;[/code] 然後在currentX位置輸出. 接下來看printChar也就是輸出部分最低層的操作咯 [code]void printChar (const byte ch) { *(word *)(VIDEO + y * 160 + x * 2) = ch | (fill_color << 8); x++; if (x >= WIDTH) newLine (); setVideoCursor (y, x); }[/code]這裏VIDEO表示顯存地址也就是0xB8000.通過 y * 160 + x 屏幕(x,y)坐標在顯存中的位置.這裏需要知道,一個字符顯示需要兩個字節,一個是ASCII碼,第二個是字符屬性代碼也就是顔色代碼.因此才必須 y * 80 * 2 + x = y * 160 + x.那麽ch | (fill_color << 8)也自然就是寫入字符及屬性代碼用的了.每寫一個字符光標位置加1,如果大于屏幕寬度WIDTH就換行.最後通過setVideoCursor設置新的光標位置.完成了整個printChar過程. 到此,把printf從上到下說了一遍.不知道各位大家感覺如何,如果說得不清楚還大家多提意見.有說得不對的地方請大家多多指教.
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 
 熱帖排行
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有