stdio本身是C标准库中最核心的格式化输出函数,可以从用户接口层、格式化引擎层、系统调用层三个方面来描述这一printf实现的过程。
1、用户接口层
printf在stdarg.h中进行结构体定义
int printf(const char *format, ...)
{
va_list_args;
va_start(args,format); //初始化参数列表,指向第一个可变参数
int ret = vfprintf(stdout,format,args); //委托给核心函数实现
va_end(args); //清理参数列表
return ret;
}这里针对printf的封装将变量转向vfprintf这个核心函数中
2、格式化引擎层:解析格式字符串
vfprintf内部是一个状态机,逐字符扫描格式字符串:
普通字符,直接写入输出缓冲区
%字符,格式说明符解析状态,依次读取标志、宽度、精度、长度、转换这几个不同的符号,比如%d就是i转换为十进制整数。
3、系统调用层:写入中端
字符串格式化后,写入stdout对应的文件流缓冲区,当遇到如下情况时,缓冲区进行依次刷新:
遇到换行符\n
缓冲区满
程序正常结束
显式调用fflush
刷新缓冲区时,最终通过write系统调用函数将数据写入文件描述符,由操作系统内核将数据送入中断设备驱动。
4、内核驱动层其一:字符显示
内核基于FrameBuffer的Linux内核标准化图形抽象接口实现字符绘制。它将屏幕封装为/dev/fb0设备,允许通过内存映射直接操作显存。要在屏幕上显示字符,核心是将字符的字模数数据写入显存对应位置。
// 1、打开FrameBuffer设备
int fb_fd = open("/dev/fb0",0_PDWR);
// 2、获取屏幕信息
struct fb_var_screeninfo vinfo;
ioctl(fd_fd, FBIOGET_VSCREENINFO, &vinfo);
// 3、映射显存到用户空间
char *fb_mem = mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fd, 0);
// 4、根据字模绘制字符
viod draw_char(int x, int y, char ch) {
// 获取字符ch的字模数据
unsigned char *bitmap = get_font_bitmap(ch);
// 逐行逐列写入显存
for(int row = 0; row < 16; row++) {
for(int col = 0; col < 9; col++) {
if (bitmap[row] & (0x80 >> col)) {
// 计算像素在显存中的偏移
int offset = ((y + row) * vinfo.xres + (x + col)) * (vinfo.bits_per_pixel / 8);
// 写入颜色值
fb_mem[offset + 0] = 255; // Blue
fb_mem[offset + 1] = 255; // Green
fb_mem[offset + 2] = 255; // Red
}
}
}
}
// 5、显示“Hello World”
draw_string(100,100,"Hello World")5、内核驱动层之二:内核环形缓冲区tty到console抽象与最终驱动
内核环形缓冲区其实就是在内核代码中构建一个fifo
内核缓冲区域中,当指针到达缓冲区的物理末端时,会自动回绕到缓冲区的起始位置。
这一结构通过两个索引来维护状态:
Head指针:指向生产者下一个可以写入数据的位置
Tail指针:指向消费者下一个可以读取数据的位置
缓冲器其实就是一个FIFO,下面是Kfifo的一个具体实现:
struct kfifo {
unsigned char *buffer; // 指向实际数据缓冲区地址
unsigned int size; // 缓冲区大小
unsigned int in; // 写入地址
unsigned int out; // 读取地址
}常用API:
kfifo_alloc()/kfifo_init()——分配或初始化环形缓冲区
kfifo_in():向缓冲区写入数据
kfifo_out():从缓冲区读取数据
kfifo_len():获取当前缓冲区中的数据长度
kfifo_space():获取缓冲区剩余可用空间
printk默认输出到物理控制台,比如tty1,TTY的全称是TeletypeWriter意思是电传打字机。
下面是一个tty结构的具体struct (/include/tty.h)
struct tty_struct {
int magic; // 魔术字
struct kerf kerf; // 引用计数器
struct device *dev; // 设备结构体,用于设备管理
// 驱动程序接口
const struct tty_operations *ops; // 指向具体TTY驱动的操作函数集
struct tty_driver *driver; // 指向所属的TTY驱动程序
// 终端状态与设置
struct termios termios; // 终端IO设置
struct termios termios_locked; // 锁定的终端设置
//数据缓冲区
struct tty_buffer *read_buf; // 读取缓冲区
struct tty_buffer *write_buf; // 写入缓冲区
。。。。。。其他部分
}