菜单

kaoqi
kaoqi
发布于 2026-07-31 / 4 阅读
0
0

stdio实现printf函数过程

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;  // 写入缓冲区

  。。。。。。其他部分
}


评论