linux開發打印及格式化

在進行android或者linux開發的過程中,打印和格式化使我們經常使用的函數,有時候有某種想法,可是不知道有哪些函數可以去實現,就算你知道是有函數的,但你可能記不住名字,參數個數,以及順序,快年底了,趁現在有空,趕緊整理出來,我可能側重內核空間部分,但對於內核空間和用戶空間的打印、格式化一般都有一一對應的函數的,可能就是名字稍微不一樣罷了,比如內核空間打印用printk,而用戶空間用printf。

內核空間打印:

1. printk

kernel\kernel\printk.c

kernel\include\linux\printk.h

printk是內核中最主要的打印函數了,其他的一些打印基本都是基於此的,對應於用戶空間的printf,參數區別在於,printk多了個打印等級。

原型: int printk(const char *fmt, ...);返回值爲打印的長度

例如,printk(KERN_INFO  "%s[%d]\n",__FUNCTION__,__LINE__) ;

在printk.h中有定義等級:

#define KERN_EMERG	"<0>"	/* system is unusable			*/
#define KERN_ALERT	"<1>"	/* action must be taken immediately	*/
#define KERN_CRIT	"<2>"	/* critical conditions			*/
#define KERN_ERR	"<3>"	/* error conditions			*/
#define KERN_WARNING	"<4>"	/* warning conditions			*/
#define KERN_NOTICE	"<5>"	/* normal but significant condition	*/
#define KERN_INFO	"<6>"	/* informational			*/
#define KERN_DEBUG	"<7>"	/* debug-level messages			*/


/* printk's without a loglevel use this.. */
#define DEFAULT_MESSAGE_LOGLEVEL CONFIG_DEFAULT_MESSAGE_LOGLEVEL

/* We show everything that is MORE important than this.. */
#define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */
#define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */


int console_printk[4] = {
	DEFAULT_CONSOLE_LOGLEVEL,	/* console_loglevel */
	DEFAULT_MESSAGE_LOGLEVEL,	/* default_message_loglevel */
	MINIMUM_CONSOLE_LOGLEVEL,	/* minimum_console_loglevel */
	DEFAULT_CONSOLE_LOGLEVEL,	/* default_console_loglevel */
};


其中console_printk數組,
數組第一項:爲控制檯打印級別,更高優先級(數值更小的)將被輸出到控制檯
數組第二項:爲默認消息打印級別,即printk不指定級別時的打印級別值
數組第三項:爲控制檯打印級別可設置的最小值(最高優先級)
數組第四項:爲控制檯打印缺省級別值

這數組四項默認值爲7,4,1,7  
通過 cat /proc/sys/kernel/printk 
能夠獲取當前的4個值,一般你的printk沒有打印出來,就是要echo改第一個值即可,當然如果你的printk沒有設置打印級別,你也可以調整第二值
我的設備獲取的值爲6,6,1,7,由於第一個值爲6,那麼小於6的打印才能輸出,debug,info的打印就不會輸出了。


所有的printk打印實際上都是放到ring buffer中的,這個環形buffer大小默認是64K,當然可以編譯的時候在General setup  --->Kernel log buffer size中修改,默認是16,即2^16=64K,或者在啓動參數中增加log_buf_len=2M。當打印超出比如64K,後面的內容會把最先打印的從buffer中擠出去,類似於FIFO。


cat  /proc/kmsg和dmesg打印的內容實際上就是從ring buffer中獲取,所以不存在打印級別的限制,打印級別限制只是在console上纔會有效。console我們先膚淺的當做就是所謂的串口吧(串口要register_console才能算console,而且能在串口中輸入shell命令,而如果沒有註冊console的串口是不會相應你的shell命令的)

比如編譯user版本安卓系統時,內核打印就是無權限的,kmsg和dmesg無權限反饋如下

/system/bin/sh: cat: /proc/kmsg: Permission denied
klogctl: Operation not permitted

只有root權限的才能看內核打印。


2. dev_xxx

設備打印也是8個類型,對應於8種打印級別,且這8個函數都是基於printk打印的。我們先說前7個,最後一個事DEBUG級別,下面單獨說明。

int dev_emerg(const struct device *dev, const char *fmt, ...);
int dev_alert(const struct device *dev, const char *fmt, ...);
int dev_crit(const struct device *dev, const char *fmt, ...);
int dev_err(const struct device *dev, const char *fmt, ...);
int dev_warn(const struct device *dev, const char *fmt, ...);
int dev_notice(const struct device *dev, const char *fmt, ...);
int dev_info(const struct device *dev, const char *fmt, ...);

實質上這7個函數先調用的是

int __dev_printk(const char *level, const struct device *dev,
struct va_format *vaf)
{
if (!dev)
return printk("%s(NULL device *): %pV", level, vaf);
return printk("%s%s %s: %pV",
     level, dev_driver_string(dev), dev_name(dev), vaf);
}


然後__dev_printk調用printk

這7個函數是直接用的,只要包含linux\device.h即可,但是能否通過console打印出來,要看/proc/sys/kernel/printk的默認console打印級別了。這7個函數唯一需要說明的就是第二個參數dev,這個參數是你當前設備指針(可能是自己自定義的結構體或者平臺結構體)展開到struct device的指針,那麼這個參數會打印出什麼呢?dev是這樣處理的

drv = ACCESS_ONCE(dev->driver);
return drv ? drv->name :
(dev->bus ? dev->bus->name :
(dev->class ? dev->class->name : " "));

即驅動名>設備總線名>設備類名,從這個優先級中取出一個作爲打印字符串

那麼dev_xxx打印的字段依次爲:打印級別,驅動名,設備名,格式化的字符串

接下來得說說dev_dbg,因爲它要打印出來,還有DEBUG開關的。必須在直接或者間接包含linux\device.h之前定義DEBUG才能使用

比如:

#define ....

#define DEBUG    1 
#include <linux/platform_device.h> 

......

dev_dbg(.....);

......


另外還有一個冗餘打印dev_vdbg,實質上他就是dev_dbg函數,要是使用dev_vdbg,需要在直接或者間接包含linux\device.h之前定義DEBUG和VERBOSE_DEBUG

#define DEBUG
#define VERBOSE_DEBUG

...

#include <linux/device.h>

...


內核空間格式化

1. sprintf/snprintf

int sprintf(char *, const char *, ...);//函數先執行buff[0]='\0';返回值爲實際輸出到buffer中的長度,包括'\0'
int snprintf(char *, size_t n,const char *, ...);
//不會執行buff[0]='\0';可以多次使用snprintf進行格式化字串的連接,返回值爲應該填充到buffer中的字串長度,當返回值大於等於sizeof(buffer),說明buffer長度不夠,格式化串被截斷了(buffer填充長度最多到n)。
比如:
    len_3 = snprintf(tlist_3,10,"this is a overflow test!\n");
    printf("len_3 = %d,tlist_3 = %s\n",len_3,tlist_3);//結果是len_3=25,但是tlist_3="this is a"

可變參數跟printf一樣用,sprintf如果buff數組空間不夠,有可能會溢出,造成程序崩潰,而snprintf很好的解決了這個問題,推薦優先使用,第二個參數n限制了格式化後填充的buffer最大長度,一般取值爲sizeof(buff)。在驅動中一般在設備屬性attr的show函數中經常使用,比如show的最後一句一般都是return snprintf(info->bus_info, sizeof(info->bus_info), "PCMCIA 0x%lx", dev->base_addr);。在show函數中,buff允許存放的長度最大值爲一頁,一般爲4096,也就是說n的大小不要超過一頁4096。


此處提一下strlcpy和strlcat問題,但是這2個函數不是標準的c函數,但是linux是支持的,使用優先級strcpy<strncpy<strlcpy,strncpy有性能問題,strcpy有越界問題,strlcpy最優先用,strlcat類同。具體詳情見文章《Strlcpy和strlcat——一致的、安全的字符串拷貝和串接函數

2. sscanf

int sscanf (const char * buf, const char * fmt, ... ...);

返回值爲Unformat 參數的個數。詳細見《C語言函數sscanf的用法》,能執行復雜的去格式轉換,簡單的一次去格式轉換下面會有介紹。

例如sscanf(buf, "%x", &parsed_rate);//將buf還原爲16進制整數。返回值爲1

  sscanf("iios/12DDWDFF@122", "%*[^/]/%[^@]", buf);
  printf("%s\n", buf);
  結果爲:12DDWDFF

3. strtoxxx

unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base) ;

返回:返回轉換後數據。

參數:cp指向字符串的開始,endp指向分析的字符串末尾的位置,base爲要用的基數(進制數),base爲0表示通過cp來自動判斷基數,函數自動可識別的基數:‘0x’表示16進制,‘0’表示8進制,其它都認定爲10進制。函數可轉換成數字的有效字符爲:[0,f]。

舉例:cp = “0x12str”,base = 0,則返回unsigned long long爲18,*endp = “str”。對於待處理字符串沒有嚴格的要求。

此類函數有以下幾種:

unsigned long simple_strtoul(const char *,char **,unsigned int);
long simple_strtol(const char *,char **,unsigned int);
unsigned long long simple_strtoull(const char *,char **,unsigned int);
long long simple_strtoll(const char *,char **,unsigned int);
在linux\kernel.h中有這麼一句:/* Obsolete, do not use.  Use kstrto<foo> instead */說明上述simple_strtoxx函數在內核中已經不推薦使用了,將來應該會踢出linux中的,又有#define strict_strtoulkstrtoul ,它的替代者是strict_strtoxxx。


int strict_strtoul(const char *cp, unsigned int base, unsigned long *res)
功能:將一個字符串轉換成unsigend long型。
返回:轉換成功返回0,否則返回負。res指向轉換後的unsigned long數據。
說明:該函數對cp指向的字符串嚴格要求,cp指向的字符串必須爲真正的unsigned long形式的字符串。字符串必須以“0x”、“0”、[0,f]開始,中間全部爲有效的字符[0,f],否則返回爲負。它會處理字符串最後的“\n”字符。

此類函數有以下幾種:

<span style="font-size:14px;">int strict_strtoul(const char *cp, unsigned int base, unsigned long *res)    
int strict_strtol(const char *cp, unsigned int base, long *res)    
int strict_strtoull(const char *cp, unsigned int base, unsigned long long *res)    
int strict_strtoll(const char *cp, unsigned int base, long long *res) </span>


用戶空間打印

用戶空間的打印無非就是printf了,沒什麼可說的。


用戶空間的格式轉化

如atoi,它對應於內核空間的simple_strtoul或者strict_strtoul

linux好像沒有itoa函數,如果你要使用itoa,ltoa,ultoa這類意義的函數去將各種整形轉換爲字符串,只要用sprintf就通吃了


字符串轉化爲數

/*字符串轉化爲數*/
#inclue <stdlib.h>
//跳過前面空格,從遇到數字或符號開始轉換,再次碰到非數字或者'\0'停止。atof等價於strtod(const char *start, NULL)
double atof(const char *str);
int atoi(const char *str);
//atol等價於strtol(const char *start, NULL, 10);
long atol(const char *str);
double strtod(const char *start, char **end);
long int strtol(const char *start, char **end, int radix);
unsigned long int strtoul(const char *start, char **end, int radix);





發佈了38 篇原創文章 · 獲贊 12 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章