目录
4. tty层的初始化:/dev/console设备文件的创建
参考:
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的系统调用
- Linux串口驱动程序(4)-数据发送_小虾米_2018的博客-CSDN博客
- userspace:如果想往控制台读写,可open("/dev/console", ....);
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; } }
- tty_open_current_tty打开当前进程对应的tty,init进程未指定当前进程的tty,于是返回NULL
- 调用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方法