在进行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_strtoul kstrtoul ,它的替代者是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);





Logo

更多推荐