首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

uClinux移植和分析(2)

uClinux移植和分析(2)

printf和标准输出


    上次写到main函数的参数传递.现在继续往下进行.最近忙实验室的事情,看了一周
的文章,也没啥进展,周末写点技术贴,放松一下:-)


    进入main函数后,就要调用printf("Hello World!");了.顺便将C语言参数传递提
一下.字符串"Hello World!\n"编译器是当作字符串常量来处理的,虽然printf是在main
内部调用,但"Hello World!\n"可不是放在main的栈中,字符串常量至少是放到.data段的
,准确说是放在只读数据段.rodata,这个我在工作站上验证了一把.假如编辑的文件名是
hello.c,首先编译生成elf格式二进制文件gcc hello.c -o hello然后用命令
objdump -s hello -s参数会将所有段信息dump出来.你会看到"Hello World!\n"位于
.rodata段.


    printf()是个标准C库函数.虽然功能简单,但实现起来却不容易.这是个和平台相关的
函数.在pc上,printf输出是输出到终端屏幕,在嵌入式设备上,一般printf()是输出到串口.
同是调用printf(),最终输出的设备却不同,从直觉的肯定是感觉printf()底层和平台是相
关的.那么printf()是怎样实现的呢?


    可以看一下C库程序的代码,这里以uClibc为例.
    int printf(const char * __restrict format, ...)
    {
    va_list arg;
    int rv;


    va_start(arg, format);
    rv = vfprintf(stdout, format, arg);
    va_end(arg);


    return rv;
    }
    printf支持字符串格式化输出,具体参数处理这里不提.可以看到printf()调用了
vfprintf(),vfprintf()第一个参数是stdout是标准输出设备.标准输出设备是个结构体,
最重要的成员就是他的描述符,其值为1.


    跟进vfprintf()函数看,里面是复杂的参数处理,因为printf()的参数形式很灵活,
所以在vfprintf()里面要对传进来的参数进行解析处理,形成最终的输出格式.有兴趣的
可以看一下,借助这个可以让你在一个没有操作系统的平台上实现你自己的printf()
函数.这样你在裸机上调程序时输出调试信息就更方便些(实际上uClinux的printk就是
这么干的).


    vfprintf()在参数处理之后,就是输出了,输出调用的是putc(),进入putc()然后
再跟进几层函数,发现调用了linux系统调用write()。呵呵,是的,输出就是借助操作
系统代码完成的。在write之前所有的代码都是C库的代码,可以说是和平台无关的。
而涉及到具体输出,就要调用操作系统提供给你的接口。系统调用的原理就是通过一定
手段(一般是trap陷阱)进入操作系统的内核空间,调用操作系统代码来完成某些任务。


    Linux系统调用针对不同平台有不同的实现方式。这个以后再讲。调用write()后,
进入内核空间,首先来到的就是sys_write(),这个函数代码位于fs/read_write.c中。
一进入sys_write(),就要根据传进来的fd描述符找到相应的file结构。对于标准输出,
fd=1,每个进程的进程控制块都有一个打开文件的数组。file结构就是根据fd在这个
数组中查找到相应的结构。找到结构后,就会调用file->write()来向外输出。具体输出
到哪里,就要看file结构对应的设备驱动是什么。一般嵌入式系统可以从串口将信息输
出,那么file->write()最底层就是调用的串口驱动的类似transmit_char的函数。

51 c8051f(f020,f040) msp430 arm(2410,2510) fpga(xc3s4000) dsp(5116 dm642) keilc vc++ matlab linux protel Ten_layerPCB mpegx h.26x Rscode Turbocode ofdm VideoBroadcasting ldpc_code(now!)
有关linux的设备驱动有很多书介绍,整个驱动的结构很复杂,我这里也没必要提了.
至于终端设备怎样挂在驱动队列里面,怎么根据标准输出的描述符找到相应的驱动结构
有兴趣的莊printf()函数看,里面是复杂的参数处理,因为printf()的参数形式很灵活,
所以在vfprintf()里面要对传进来的参数进行解析处理,形成最终的输出格式.有兴趣的
可以看一下,借助这个可以让你在一个没有操作系统的平台上实现你自己的printf()
函数.这样你在裸机上调程序时输出调试信息就更方便些(实际上uClinux的printk就是
这么干的).

vfprintf()在参数处理之后,就是输出了,输出调用的是putc(),进入putc()然后
再跟进几层函数,发现调用了linux系统调用write()。呵呵,是的,输出就是借助操作
系统代码完成的。在write之前所有的代码都是C库的代码,可以说是和平台无关的。
而涉及到具体输出,就要调用操作系统提供给你的接口。系统调用的原理就是通过一定
手段(一般是trap陷阱)进入操作系统的内核空间,调用操作系统代码来完成某些任务。

Linux系统调用针对不同平台有不同的实现方式。这个以后再讲。调用write()后,
进入内核空间,首先来到的就是sys_write(),这个函数代码位于fs/read_write.c中。
一进入sys_write(),就要根据传进来的fd描述符找到相应的file结构。对于标准输出,
fd=1,每个进程的进程控制块都有一个打开文件的数组。file结构就是根据fd在这个
数组中查找到相应的结构。找到结构后,就会调用file->write()来向外输出。具体输出
到哪里,就要看file结构对应的设备驱动是什么。一般嵌入式系统可以从串口将信息输
出,那么file->write()最底层就是调用的串口驱动的类似transmit_char的函数。

有关linux的设备驱动有很多书介绍,整个驱动的结构很复杂,我这里也没必要提了.
至于终端设备怎样挂在驱动队列里面,怎么根据标准输出的描述符找到相应的驱动结构
有兴趣的请查阅相关资料.
--


手段(一般是trap陷阱)进入操作系统的内核空间,调用操作系统代码来完成某些任务。

Linux系统调用针对不同平台有不同的实现方式。这个以后再讲。调用write()后,
进入内核空间,首先来到的就是sys_write(),这个函数代码位于fs/read_write.c中。
一进入sys_write(),就要根据传进来的fd描述符找到相应的file结构。对于标准输出,
fd=1,每个进程的进程控制块都有一个打开文件的数组。file结构就是根据fd在这个
数组中查找到相应的结构。找到结构后,就会调用file->write()来向外输出。具体输出
到哪里,就要看file结构对应的设备驱动是什么。一般嵌入式系统可以从串口将信息输
出,那么file->write()最底层就是调用的串口驱动的类似transmit_char的函数。

有关linux的设备驱动有很多书介绍,整个驱动的结构很复杂,我这里也没必要提了.
至于终端设备怎样挂在驱动队列里面,怎么根据标准输出的描述符找到相应的驱动结构
有兴趣的请查阅相关资料.
51 c8051f(f020,f040) msp430 arm(2410,2510) fpga(xc3s4000) dsp(5116 dm642) keilc vc++ matlab linux protel Ten_layerPCB mpegx h.26x Rscode Turbocode ofdm VideoBroadcasting ldpc_code(now!)
返回列表