printf是如何打印到终端

目录

0.整体流程

1. bootloader传入参数 console

2.kernel是在哪里设定 输入console参数

3. console的初始化

4. tty层的初始化:/dev/console设备文件的创建

5. 驱动层的初始化:以串口为例

6. write的系统调用

7. 总结:


参考:

linux kernel下输入输出console如何实现_kerneler_的博客-CSDN博客

Linux串口驱动程序(4)-数据发送_小虾米_2018的博客-CSDN博客

0.整体流程

  • 用户层通过打开/dev/console,设备。再调用 read/wrtie方法来调用至底层的串口收发!!

1. bootloader传入参数 console

  • bootargs 保存着 uboot 传递给 Linux 内核的参数
  • bootloader可以看做是一套裸机程序,它去启动了kernel这个程序,bootloadr在启动kernel的时候,会传入参数:”console=ttySAC0, 115200”
  • console 用来设置 linux 终端(或者叫控制台),也就是通过什么设备来和 Linux 进行交互

2.kernel是在哪里设定 输入console参数

参考:linux kernel的cmdline参数解析原理分析_kerneler_的博客-CSDN博客_linux parse_args

  • 在/kernel/printk.c中:拿到cmdline的参数,通过console_setup()函数来解析参数
static int __init console_setup(char *str)
{
    char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for index */
    char *s, *options, *brl_options = NULL;
    int idx; 
 
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
    if (!memcmp(str, "brl,", 4)) {
        brl_options = "";
        str += 4;
    } else if (!memcmp(str, "brl=", 4)) {
        brl_options = str + 4; 
        str = strchr(brl_options, ',');
        if (!str) {
            printk(KERN_ERR "need port name after brl=\n");
            return 1;
        }    
        *(str++) = 0; 
    }    
#endif
 
    /*
     * Decode str into name, index, options.
     */
    if (str[0] >= '0' && str[0] <= '9') {
        strcpy(buf, "ttyS");
        strncpy(buf + 4, str, sizeof(buf) - 5);
    } else {
        strncpy(buf, str, sizeof(buf) - 1);
    }
    buf[sizeof(buf) - 1] = 0;
    if ((options = strchr(str, ',')) != NULL)
        *(options++) = 0;
#ifdef __sparc__
    if (!strcmp(str, "ttya"))
        strcpy(buf, "ttyS0");
    if (!strcmp(str, "ttyb"))
        strcpy(buf, "ttyS1");
#endif
    for (s = buf; *s; s++)
        if ((*s >= '0' && *s <= '9') || *s == ',')
            break;
    idx = simple_strtoul(s, NULL, 10);
    *s = 0;
 
    __add_preferred_console(buf, idx, options, brl_options);
    console_set_on_cmdline = 1;
    return 1;
}

__setup("console=", console_setup);
  • 最终调用__add_preferred_console()函数将配置得到的参数

3. console的初始化

  • 通过start_kernel()来调用console_init()
void __init console_init(void)
{
    initcall_t *call;

    /* Setup the default TTY line discipline. */
    n_tty_init();

    /*
     * set up the console device so that later boot sequences can
     * inform about problems etc..
     */
    call = __con_initcall_start;
    while (call < __con_initcall_end) {
        (*call)();
        call++;
    }
}
  • n_tty_init()完成对ldisc层的初始化

4. tty层的初始化:/dev/console设备文件的创建

  • 在linux系统初始化的时候会去调用tty_init()进行初始化:从而初始化/dev/conosle,这里register_chrdev_region指定了设备号

static const struct file_operations console_fops = {
    .llseek     = no_llseek,
    .read       = tty_read,
    .write      = redirected_tty_write,
    .poll       = tty_poll,
    .unlocked_ioctl = tty_ioctl,
    .compat_ioctl   = tty_compat_ioctl,
    .open       = tty_open,
    .release    = tty_release,
    .fasync     = tty_fasync,
};
int __init tty_init(void)
{
    cdev_init(&tty_cdev, &tty_fops);
    if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
        register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
        panic("Couldn't register /dev/tty driver\n");
    device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
 //初始化 /dev/console
    cdev_init(&console_cdev, &console_fops);
    if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
        register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
        panic("Couldn't register /dev/console driver\n");
    consdev = device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL,
                  "console");
    if (IS_ERR(consdev))
        consdev = NULL;
    else
        WARN_ON(device_create_file(consdev, &dev_attr_active) < 0);
    
#ifdef CONFIG_VT
    vty_init(&console_fops);
#endif
    return 0;
}

  •  文件系统初始化完成会打开一个/dev/console的设备
/* Open the /dev/console on the rootfs, this should never fail */
    if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
        printk(KERN_WARNING "Warning: unable to open an initial console.\n");
 

5. 驱动层的初始化:以串口为例

  • 初始化一个uart_driver 的结构体,并用uart_register_driver()去注册这驱动
static struct uart_driver s3c24xx_uart_drv = {
	.owner		= THIS_MODULE,
	.dev_name	= "s3c2410_serial",
	.nr		= CONFIG_SERIAL_SAMSUNG_UARTS,
	.cons		= S3C24XX_SERIAL_CONSOLE,
	.driver_name	= S3C24XX_SERIAL_NAME,
	.major		= S3C24XX_SERIAL_MAJOR,
	.minor		= S3C24XX_SERIAL_MINOR,
};
static int __init s3c24xx_serial_modinit(void)
{
	int ret;
 
	ret = uart_register_driver(&s3c24xx_uart_drv);
	if (ret < 0) {
		printk(KERN_ERR "failed to register UART driver\n");
		return -1;
	}
 
	return 0;
}

6. write的系统调用

int fd = open("/dev/console");,这里会去调用 tty设备的open方法:tty_open (tty_init指定)

static int tty_open(struct inode *inode, struct file *filp)
{
    struct tty_struct *tty;
    int noctty, retval;
    struct tty_driver *driver = NULL;
    int index;
    dev_t device = inode->i_rdev;
    unsigned saved_flags = filp->f_flags;
    
    nonseekable_open(inode, filp);
 
retry_open:
    retval = tty_alloc_file(filp);
    if (retval) 
        return -ENOMEM;
    
    noctty = filp->f_flags & O_NOCTTY;
    index  = -1;
    retval = 0;
 
    mutex_lock(&tty_mutex);
    tty_lock();
 
    tty = tty_open_current_tty(device, filp);
    if (IS_ERR(tty)) {
        retval = PTR_ERR(tty);
        goto err_unlock;
    } else if (!tty) {
        driver = tty_lookup_driver(device, filp, &noctty, &index);
        if (IS_ERR(driver)) {
            retval = PTR_ERR(driver);
            goto err_unlock;
        }  /* check whether we're reopening an existing tty */
        tty = tty_driver_lookup_tty(driver, inode, index);
        if (IS_ERR(tty)) {
            retval = PTR_ERR(tty);
            goto err_unlock;
        }
    }

  1. tty_open_current_tty打开当前进程对应的tty,init进程未指定当前进程的tty,于是返回NULL
  2. 调用tty_lookup_driver来找到console_driver---tty_driver,并且返回这个tty的驱动!!!
  • 如 0的图  write -->tty_write--->n_tty_write--->调用驱动层的write方法:这就和裸机的操作没区别了!

7. 总结:

  • 打开了控制台的设备: /dev/consloe,并通过int fd = open ("/dev/consloe")
  • write(fd , data);  往这个设备写数据,通过内核的调用栈:ttycore层--->ldisc层--->和设备匹配上的驱动的write方法
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值