printf 重新实现put_再识printf

前言

printf函数,对于C程序员来说,熟悉得不能再熟悉,基础得不能再基础了。不管是什么版本的C语言教材,按照惯例,第一个示例必然是用printf打印一串hello world。

我当时学C语言的时候,也只是依葫芦画瓢把示例代码输入电脑,按步骤编译、运行,屏幕上果然出现了hello world。当时也没有去思考程序是如何将hello world显示到屏幕上的,按当时的水平,也想不出个所以然。

前两天偶然刷到一个视频:《linux嵌入式开发:字符设备驱动》,里面讲到了linux下printf打印字符到屏幕的整个流程,视频截图如下:

18d8df862106333deb389d5f438112ef.png

视频截图

作者讲的整个流程大致如下:

  1. printf
  2. write
  3. sys_write
  4. LCD_write

对于该流程我实在不敢苟同,视频作者应该不了解显示驱动。对于一个显示驱动或者LCD驱动来说,最重要的就是调用register_framebuffer注册一个fb驱动,fb驱动中最重要的就是实现一套struct fb_ops接口。

而这套接口中,根本就没有write函数,也没有直接输出字符的函数,有的只是针对屏幕像素、光标进行操作的接口函数:

struct fb_ops {    ...    /* Draws a rectangle */    void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);    /* Copy data from area to another */    void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);    /* Draws a image to the display */    void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);    /* Draws cursor */    int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);    ...}

视频作者在不清楚显示驱动的情况下,有点猜测和想当然了,这也勾起了我想重新梳理整个流程的兴趣。

printf流程

整个调用流程如下图所示:

2029b4b88ede87080720b23eb185e3bd.png

printf调用流程

1.调用printf打印字符串:printf(string);

2.write(1, string, len);将字符串写入标准输出stdout,在linux字符模式控制台下,标准输出指向/dev/tty[n],其中n为当前虚拟控制台(在tty章节中,也称它为虚拟终端VT)的编号。

3.sys_write,该函数位于内核空间,是系统调用入口。

4.vfs_write,调用file->f_op->write,f_op接口在open设备文件时安装,实际指向tty_write。

ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,            loff_t *pos){    if (file->f_op->write)        return file->f_op->write(file, p, count, pos);    else if (file->f_op->write_iter)        return new_sync_write(file, p, count, pos);    else        return -EINVAL;}static const struct file_operations tty_fops = {    .llseek     = no_llseek,    .read       = tty_read,    .write      = tty_write,    .poll       = tty_poll,    .unlocked_ioctl = tty_ioctl,    .compat_ioctl   = tty_compat_ioctl,    .open       = tty_open,    .release    = tty_release,    .fasync     = tty_fasync,};

5.tty_write,获取tty的线路规程ld,并调用ld->ops->write,实际指向n_tty_write。

static struct tty_ldisc_ops n_tty_ops = {    .magic           = TTY_LDISC_MAGIC,    .name            = "n_tty",    .open            = n_tty_open,    .close           = n_tty_close,    .flush_buffer    = n_tty_flush_buffer,    .read            = n_tty_read,    .write           = n_tty_write,    .ioctl           = n_tty_ioctl,    .set_termios     = n_tty_set_termios,    .poll            = n_tty_poll,    .receive_buf     = n_tty_receive_buf,    .write_wakeup    = n_tty_write_wakeup,    .receive_buf2    = n_tty_receive_buf2,};

6.n_tty_write,调用tty->ops->write,实际指向con_write。

static const struct tty_operations con_ops = {    .install = con_install,    .open = con_open,    .close = con_close,    .write = con_write,    .write_room = con_write_room,    .put_char = con_put_char,    .flush_chars = con_flush_chars,    .chars_in_buffer = con_chars_in_buffer,    .ioctl = vt_ioctl,#ifdef CONFIG_COMPAT    .compat_ioctl = vt_compat_ioctl,#endif    .stop = con_stop,    .start = con_start,    .throttle = con_throttle,    .unthrottle = con_unthrottle,    .resize = vt_resize,    .shutdown = con_shutdown};

7.con_write,调用vc->vc_sw->con_putcs,实际指向fbcon_putcs。

static const struct consw fb_con = {    .owner          = THIS_MODULE,    .con_startup        = fbcon_startup,    .con_init       = fbcon_init,    .con_deinit         = fbcon_deinit,    .con_clear      = fbcon_clear,    .con_putc       = fbcon_putc,    .con_putcs      = fbcon_putcs,    .con_cursor         = fbcon_cursor,    .con_scroll         = fbcon_scroll,    .con_switch         = fbcon_switch,    .con_blank      = fbcon_blank,    .con_font_set       = fbcon_set_font,    .con_font_get       = fbcon_get_font,    .con_font_default   = fbcon_set_def_font,    .con_font_copy      = fbcon_copy_font,    .con_set_palette    = fbcon_set_palette,    .con_scrolldelta    = fbcon_scrolldelta,    .con_set_origin     = fbcon_set_origin,    .con_invert_region  = fbcon_invert_region,    .con_screen_pos     = fbcon_screen_pos,    .con_getxy      = fbcon_getxy,    .con_resize             = fbcon_resize,    .con_debug_enter    = fbcon_debug_enter,    .con_debug_leave    = fbcon_debug_leave,};

8.fbcon_putcs,调用fbcon_ops->putcs,实际指向bit_putcs。

void fbcon_set_bitops(struct fbcon_ops *ops){    ops->bmove = bit_bmove;    ops->clear = bit_clear;    ops->putcs = bit_putcs;    ops->clear_margins = bit_clear_margins;    ops->cursor = bit_cursor;    ops->update_start = bit_update_start;    ops->rotate_font = NULL;    if (ops->rotate)        fbcon_set_rotate(ops);}

9.bit_putcs,该函数完成字符串的光栅化操作,并调用fbops->fb_imageblit将结果复制到屏幕上,fb_imageblit实际指向显示驱动提供的回调函数。

static struct fb_ops foo_fb_ops = {    .owner          = THIS_MODULE,    .fb_imageblit   = foo_imageblit,};

10.foo_imageblit,将光栅化之后的图像从系统内存复制到显示帧存指定位置,最终完成上屏显示操作。

一个简单的printf函数,为了输出字符到屏幕,从上到下经历了:libc -> vfs -> tty -> line discipline -> vt -> fbcon -> fbdev -> hardware,最终才被显示出来。

如果控制台是串口或网络终端,则整个过程就要简单一些;如果控制台是图形窗口下的终端,则整个过程更加复杂,此处不再一一分析。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值