static char sprint_buf[1024]; //定义一个buf存储空间来存放参数;int printf(const char *fmt, ...){
va_list args;int n;va_start(args, fmt);n = vsprintf(sprint_buf, fmt, args); //主要用于输出格式的匹配工作;va_end(args);if (console_ops.write)console_ops.write(sprint_buf, n); //终端的操作,注册和初始化串口;return n;
}
int vsprintf(char *buf, const char *fmt, va_list args){
int len;unsigned long long num;int i, base;char * str;const char *s;int flags; /* flags to number() */int field_width; /* width of output field */int precision; /* min. # of digits for integers; max number of chars for from string */int qualifier; /* 'h', 'l', or 'L' for integer fields *//* 'z' support added 23/7/1999 S.H. *//* 'z' changed to 'Z' --davidm 1/25/99 */for (str=buf ; *fmt ; ++fmt) {if (*fmt != '%') {*str++ = *fmt;continue;}/* process flags */flags = 0;repeat:++fmt; /* this also skips first '%' */switch (*fmt) {case '-': flags |= LEFT; goto repeat;case '+': flags |= PLUS; goto repeat;case ' ': flags |= SPACE; goto repeat;case '#': flags |= SPECIAL; goto repeat;case '0': flags |= ZEROPAD; goto repeat;}/* get field width */field_width = -1;if ('0' <= *fmt && *fmt <= '9')field_width = skip_atoi(&fmt);else if (*fmt == '*') {++fmt;/* it's the next argument */field_width = va_arg(args, int);if (field_width < 0) {field_width = -field_width;flags |= LEFT;}}
/* get the precision */precision = -1;if (*fmt == '.') {++fmt;if ('0' <= *fmt && *fmt <= '9')precision = skip_atoi(&fmt);else if (*fmt == '*') {++fmt;/* it's the next argument */precision = va_arg(args, int);}if (precision < 0)precision = 0;}
/* get the conversion qualifier */qualifier = -1;if (*fmt == 'l' && *(fmt + 1) == 'l') {qualifier = 'q';fmt += 2;} else if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L'|| *fmt == 'Z') {qualifier = *fmt;++fmt;}
/* default base */base = 10;
switch (*fmt) {case 'c':if (!(flags & LEFT))while (--field_width > 0)*str++ = ' ';*str++ = (unsigned char) va_arg(args, int);while (--field_width > 0)*str++ = ' ';continue;
case 's':s = va_arg(args, char *);if (!s)s = "";
len = strnlen(s, precision);
if (!(flags & LEFT))while (len < field_width--)*str++ = ' ';for (i = 0; i < len; ++i)*str++ = *s++;while (len < field_width--)*str++ = ' ';continue;
case 'p':if (field_width == -1) {field_width = 2*sizeof(void *);flags |= ZEROPAD;}str = number(str,(unsigned long) va_arg(args, void *), 16,field_width, precision, flags);continue;case 'n':if (qualifier == 'l') {long * ip = va_arg(args, long *);*ip = (str - buf);} else if (qualifier == 'Z') {size_t * ip = va_arg(args, size_t *);*ip = (str - buf);} else {int * ip = va_arg(args, int *);*ip = (str - buf);}continue;
case '%':*str++ = '%';continue;
/* integer number formats - set up the flags and "break" */case 'o':base = 8;break;
case 'X':flags |= LARGE;case 'x':base = 16;break;
case 'd':case 'i':flags |= SIGN;case 'u':break;
default:*str++ = '%';if (*fmt)*str++ = *fmt;else--fmt;continue;}if (qualifier == 'l') {num = va_arg(args, unsigned long);if (flags & SIGN)num = (signed long) num;} else if (qualifier == 'q') {num = va_arg(args, unsigned long long);if (flags & SIGN)num = (signed long long) num;} else if (qualifier == 'Z') {num = va_arg(args, size_t);} else if (qualifier == 'h') {num = (unsigned short) va_arg(args, int);if (flags & SIGN)num = (signed short) num;} else {num = va_arg(args, unsigned int);if (flags & SIGN)num = (signed int) num;}str = number(str, num, base, field_width, precision, flags);}*str = '\0';return str-buf;
}
从这个函数我们可以看出printf的格式符的重要性,以及它所支持的所有输出格式,在这个函数中调用了两个重要的函数,下面我们来介绍这俩个重要的函数函数的功能;
static char * number(char * str, unsigned long long num, int base, int size, int precision, int type){
char c,sign,tmp[66];const char *digits="0123456789abcdefghijklmnopqrstuvwxyz";int i;if (type & LARGE)digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";if (type & LEFT)type &= ~ZEROPAD;if (base < 2 || base > 36)return 0;c = (type & ZEROPAD) ? '0' : ' ';sign = 0;if (type & SIGN) {if ((signed long long)num < 0) {sign = '-';num = - (signed long long)num;size--;} else if (type & PLUS) {sign = '+';size--;} else if (type & SPACE) {sign = ' ';size--;}}if (type & SPECIAL) {if (base == 16)size -= 2;else if (base == 8)size--;}i = 0;if (num == 0)tmp[i++]='0';else while (num != 0) {tmp[i++] = digits[do_div(num, base)];}if (i > precision)precision = i;size -= precision;if (!(type&(ZEROPAD+LEFT)))while(size-->0)*str++ = ' ';if (sign)*str++ = sign;if (type & SPECIAL) {if (base==8)*str++ = '0';else if (base==16) {*str++ = '0';*str++ = digits[33];}}if (!(type & LEFT))while (size-- > 0)*str++ = c;while (i < precision--)*str++ = '0';while (i-- > 0)*str++ = tmp[i];while (size-- > 0)*str++ = ' ';return str;
}
这个函数的功能主要是将数字字符串按2,8,16进制的形式输出结果的处理,以及对输出的结果正负的处理和空格的处理。同时也解决了数据的输出对齐问题,从而让数据根据我们想要的形式输出结果,下面我们来介绍下该函数所用到的一些宏的定义,以便于帮助我们来理解该函数的功能。
#define ZEROPAD 1 /* pad with zero */#define SIGN 2 /* unsigned/signed long */#define PLUS 4 /* show plus */#define SPACE 8 /* space if plus */#define LEFT 16 /* left justified */#define SPECIAL 32 /* 0x */#define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */对于这些宏我就不做过多的解释了,只要将它们代入具体的宏的位置去就可以了,它们只是基本的宏替换的功能而已,没有其它过多的用法了,看到这个宏定义,让我想到了枚举,其实我可以用枚举的方法来定义这些宏定义,只是在初始化的时候有点区别,枚举的第一个成员默认被分配为0,之后每个成员要是没有赋值它将会在前一个成员的基础上加一,当让枚举和宏定义还是有区别的,宏定义是在编译器编译时对宏进行宏展开的工作,所以它不分配内存空间,而枚举就有些不一样了,他是结构类型,所以在为它初始化后它会占用内存空间,所以它会分配内存;还是继续来说说另外一个重要的函数的功能吧!
static int skip_atoi(const char **s){int i, c;for (i = 0; '0' <= (c = **s) && c <= '9'; ++*s)i = i*10 + c - '0';return i;}看到这个函数我相信我们大家都很熟悉了,这个函数和我们之前调用的atoi的功能差不多,本函数主要是主要是将字符型数字转化为十进制数字的功能,它是通过一位一位的返回模式转化的。
下面我们再来看看printf函数的另外的一个函数的功能,首先来看看 console_ops 结构体;
struct console_ops console_ops;/* Console operations */struct console_ops {int (*open)(void);void (*write)(const char *buf, int len);void (*edit_cmdline)(char *buf, int len);void (*close)(void);void *data;};
另外一个函数就是这个结构的write成员函数,下面我们来看看它的功能。
int serial_console_init(void){
void *devp;int rc = -1;devp = serial_get_stdout_devp();if (devp == NULL)goto err_out;if (dt_is_compatible(devp, "ns16550") ||dt_is_compatible(devp, "pnpPNP,501"))rc = ns16550_console_init(devp, &serial_cd);else if (dt_is_compatible(devp, "marvell,mv64360-mpsc"))rc = mpsc_console_init(devp, &serial_cd);else if (dt_is_compatible(devp, "fsl,cpm1-scc-uart") ||dt_is_compatible(devp, "fsl,cpm1-smc-uart") ||dt_is_compatible(devp, "fsl,cpm2-scc-uart") ||dt_is_compatible(devp, "fsl,cpm2-smc-uart"))rc = cpm_console_init(devp, &serial_cd);else if (dt_is_compatible(devp, "fsl,mpc5200-psc-uart"))rc = mpc5200_psc_console_init(devp, &serial_cd);else if (dt_is_compatible(devp, "xlnx,opb-uartlite-1.00.b") ||dt_is_compatible(devp, "xlnx,xps-uartlite-1.00.a"))rc = uartlite_console_init(devp, &serial_cd);/* Add other serial console driver calls here */if (!rc) {console_ops.open = serial_open;console_ops.write = serial_write;console_ops.close = serial_close;console_ops.data = &serial_cd;if (serial_cd.getc)console_ops.edit_cmdline = serial_edit_cmdline;return 0;}err_out:return -1;
}当函数执行时,console_ops.write函数就相当于执行serial_write函数,我们来看看它又做了些什么事情;
static void serial_write(const char *buf, int len){struct serial_console_data *scdp = console_ops.data;while (*buf != '\0')scdp->putc(*buf++);}
由上面的console_ops结构体我们已经知道了它有一个data成员,下面我们来具体看看serial_console_data结构的具体内容;
struct serial_console_data {
int (*open)(void);void (*putc)(unsigned char c);unsigned char (*getc)(void);u8 (*tstc)(void);void (*close)(void);
看到这个结构体就是一个操作串口的操作集合,我们只要调用它就可以操作串口了,serial_write函数主要是操作它的putc函数来输出内容到串口,下面看看putc函数,通过查找得知它又调用其它函数;};
这个函数的具体内容就不看了,就去看看cpm_serial_putc函数的具体实现,来看看它实现什么功能;int cpm_console_init(void *devp, struct serial_console_data *scdp){void *vreg[2];u32 reg[2];int is_smc = 0, is_cpm2 = 0;void *parent, *muram;void *muram_addr;unsigned long muram_offset, muram_size;
if (dt_is_compatible(devp, "fsl,cpm1-smc-uart")) {is_smc = 1;} else if (dt_is_compatible(devp, "fsl,cpm2-scc-uart")) {is_cpm2 = 1;} else if (dt_is_compatible(devp, "fsl,cpm2-smc-uart")) {is_cpm2 = 1;is_smc = 1;}
if (is_smc) {enable_port = smc_enable_port;disable_port = smc_disable_port;} else {enable_port = scc_enable_port;disable_port = scc_disable_port;}
if (is_cpm2)do_cmd = cpm2_cmd;elsedo_cmd = cpm1_cmd;
if (getprop(devp, "fsl,cpm-command", &cpm_cmd, 4) < 4)return -1;
if (dt_get_virtual_reg(devp, vreg, 2) < 2)return -1;
if (is_smc)smc = vreg[0];elsescc = vreg[0];
param = vreg[1];
parent = get_parent(devp);if (!parent)return -1;
if (dt_get_virtual_reg(parent, &cpcr, 1) < 1)return -1;
muram = finddevice("/soc/cpm/muram/data");if (!muram)return -1;
/* For bootwrapper-compatible device trees, we assume that the first* entry has at least 128 bytes, and that #address-cells/#data-cells* is one for both parent and child.*/
if (dt_get_virtual_reg(muram, &muram_addr, 1) < 1)return -1;
if (getprop(muram, "reg", reg, 8) < 8)return -1;
muram_offset = reg[0];muram_size = reg[1];
/* Store the buffer descriptors at the end of the first muram chunk.* For SMC ports on CPM2-based platforms, relocate the parameter RAM* just before the buffer descriptors.*/
cbd_offset = muram_offset + muram_size - 2 * sizeof(struct cpm_bd);
if (is_cpm2 && is_smc) {u16 *smc_base = (u16 *)param;u16 pram_offset;
pram_offset = cbd_offset - 64;pram_offset = _ALIGN_DOWN(pram_offset, 64);
disable_port();out_be16(smc_base, pram_offset);param = muram_addr - muram_offset + pram_offset;}
cbd_addr = muram_addr - muram_offset + cbd_offset;
scdp->open = cpm_serial_open;scdp->putc = cpm_serial_putc;scdp->getc = cpm_serial_getc;scdp->tstc = cpm_serial_tstc;
return 0;}
static void cpm_serial_putc(unsigned char c){while (tbdf->sc & 0x8000)barrier(); //执行空操作;sync();tbdf->addr[0] = c;eieio(); //上下文同步;tbdf->sc |= 0x8000;}
针对barrier函数和eieio函数的具体实现做一些说明:
static inline void barrier(void){asm volatile("" : : : "memory"); //执行空指令,但是消耗没存空间;}
static inline void eieio(void){__asm__ __volatile__ ("eieio" : : : "memory"); //同上}
eieio 是上下文同步指令。“上下文同步”指的是:处理器内核包含着多个独立的执行单元,所以它能够并行的执行多个指令并且是乱序的。上下文同步指令用于需要严格秩序的地方,进行强制严格的指令顺序。eieio代表“强制按顺序执行IO”。在执行过程中,加载/存储单元等待前一个访问结束之后再开始运行加载/存储指令。eieio的目的就是为了防止执行过程中的随意加载和存储。
我们再来看看tbdf具体是什么结构,看看的结构体的具体作用;
static struct cpm_bd *tbdf, *rbdf;struct cpm_bd {u16 sc; /* Status and Control */u16 len; /* Data length in buffer */u8 *addr; /* Buffer address in host memory */};
从该结构体的可以看出,putc最后存储的内容其实是存在了buff空间里了,但是疑问也同时产生了tbdf结构体是在什么时候被初始化的,初始化值是什么,带着疑问我们继续来往下看看;
static int cpm_serial_open(void){
disable_port();out_8(¶m->rfcr, 0x10);out_8(¶m->tfcr, 0x10);out_be16(¶m->mrblr, 1);out_be16(¶m->maxidl, 0);out_be16(¶m->brkec, 0);out_be16(¶m->brkln, 0);out_be16(¶m->brkcr, 0);rbdf = cbd_addr;rbdf->addr = (u8 *)rbdf - 1;rbdf->sc = 0xa000;rbdf->len = 1;
tbdf = rbdf + 1;tbdf->addr = (u8 *)rbdf - 2;tbdf->sc = 0x2000;tbdf->len = 1;sync();out_be16(¶m->rbase, cbd_offset);out_be16(¶m->tbase, cbd_offset + sizeof(struct cpm_bd));do_cmd(CPM_CMD_INIT_RX_TX);enable_port();return 0;
从这个函数我们可以看出它对tbdf的初始化工作,我们可以根据它的初始化内容结合putc函数来理解数据的输出功能,do_cmd函数出现过多次,我们来看看这个函数,再看这个函数之前我先来看看几个宏定义;}
下面来看看do_cmd函数的实现,根据函数的查找我们可以看到do_cmd函数其实是在cpm_console_init函数里面被初始化的,实际上它是调用cpm2_cmd(do_cmd = cpm2_cmd)函数;#define CPM_CMD_STOP_TX 4#define CPM_CMD_RESTART_TX 6#define CPM_CMD_INIT_RX_TX 0
针对这个函数我们来看看它调用的函数的功能,由于它内嵌汇编,所以我们要看看它的汇编指令的意思,结合它一起来理解该函数的功能,先来看看函数本身吧;static void cpm2_cmd(int op){while (in_be32(cpcr) & 0x10000);out_be32(cpcr, op | cpm_cmd | 0x10000);while (in_be32(cpcr) & 0x10000);}
static inline unsigned in_be32(const volatile unsigned *addr){unsigned ret;__asm__ __volatile__("lwz%U1%X1 %0,%1; twi 0,%0,0; isync": "=r" (ret) : "m" (*addr));return ret;}
static inline unsigned in_be32(const volatile unsigned *addr){unsigned ret;__asm__ __volatile__("lwz%U1%X1 %0,%1; twi 0,%0,0; isync": "=r" (ret) : "m" (*addr));return ret;}
lwz rD,d(rA) ;EA=(rA|0)+d,从EA处读取4个字节的数,并加载到rD。
lwz%U1%X1 %0,%1; 将%1的内容加载到%0中去;
我们就先介绍到这里,就不分析细节方面的函数了,如果要深入的理解它可以从细节方面的着手,本文主要是分析printf函数的OS的基本的调用过程,是为让初学者对它有个大概的认识,以便于后续的学习,当然本文跟多的是我个人的理解,所以中间肯定存在理解错误的地方;在后续工作中我将会深入的去了解它的具体的过程,并且会写成文档收集起来以供有需要的人学习。twi 0,%0,0; 这是内核的寄存器的配置,具体执行什么就需要根据具体的芯片指令手册进行查找了,这里不作具体介绍;isync 这个是同步数据的指令;
: "=r" (ret) “=r”表示以寄存器变量的形式输出数据,然后将数据给ret变量,它对应上面指令的%0;: "m" (*addr) “m” 表示输入的是内存中的变量,它对应的是%1,也就是将*addr替换%1;