首先在init/main.c的init函数中建立了系统的stdin和stdout if (open("/dev/null", O_RDWR, 0) < 0) printk("Warning: unable to open an initial console./n"); (void) dup(0); (void) dup(0);打开的/dev/null设备,这也是导致busybox开始printf不能显示的原因。而且显然在busybox中重新建立了stdin和stdout,于是我在busybox源码的init/init.c的init_main函数中找到了以下代码 /* Figure out where the default console should be */ console_init(); /* Close whatever files are open, and reset the console. */ close(0); close(1); close(2); if (device_open(console, O_RDWR | O_NOCTTY) == 0) { set_term(0); close(0); } //这里好像没有建立stdin和stdout啊,反正肯定有新建的改成打开/dev/console设备,就可以在busybox中添加打印语句察看流程了。只是不知道哪里出了问题,我这里用reboot重启就会死掉,所以还是改回/dev/null了,等有空再仔细看看busybox了,这里先介绍内核是怎样把console定位到ttyS0的,因为我们真正的控制台设备是ttyS0。找到drivers/char/mem.c中的chr_dev_init函数,是用__initcall(chr_dev_init)注册使其在初始化的时候运行的。该函数注册了大部分的字符设备,null,mem,console,tty等等它tty_init()(drivers/char/tty_io.c)来注册tty和console等设备,代码如下 memset(&dev_tty_driver, 0, sizeof(struct tty_driver)); dev_tty_driver.magic = TTY_DRIVER_MAGIC; dev_tty_driver.driver_name = "/dev/tty"; dev_tty_driver.name = dev_tty_driver.driver_name + 5; dev_tty_driver.name_base = 0; dev_tty_driver.major = TTYAUX_MAJOR; dev_tty_driver.minor_start = 0; dev_tty_driver.num = 1; dev_tty_driver.type = TTY_DRIVER_TYPE_SYSTEM; dev_tty_driver.subtype = SYSTEM_TYPE_TTY; if (tty_register_driver(&dev_tty_driver)) panic("Couldn't register /dev/tty driver/n");
dev_syscons_driver = dev_tty_driver; dev_syscons_driver.driver_name = "/dev/console"; dev_syscons_driver.name = dev_syscons_driver.driver_name + 5; dev_syscons_driver.major = TTYAUX_MAJOR; dev_syscons_driver.minor_start = 1; dev_syscons_driver.type = TTY_DRIVER_TYPE_SYSTEM; dev_syscons_driver.subtype = SYSTEM_TYPE_SYSCONS;
if (tty_register_driver(&dev_syscons_driver)) panic("Couldn't register /dev/console driver/n");
tty 设备 major=5 minor=0consle 设备 major=5 minor=1
看一下它调用的tty_register_driver函数int tty_register_driver(struct tty_driver *driver){ int error; int i;
if (driver->flags & TTY_DRIVER_INSTALLED) return 0;
error = devfs_register_chrdev(driver->major, driver->name, &tty_fops); if (error < 0) return error; else if(driver->major == 0) driver->major = error;
if (!driver->put_char) driver->put_char = tty_default_put_char; driver->prev = 0; driver->next = tty_drivers; if (tty_drivers) tty_drivers->prev = driver; tty_drivers = driver; if ( !(driver->flags & TTY_DRIVER_NO_DEVFS) ) { for(i = 0; i < driver->num; i++) tty_register_devfs(driver, 0, driver->minor_start + i); } proc_tty_register_driver(driver); return error;}
使用了devfs_register_chrdev来注册设备,而下来又用tty_register_devfs又一次注册设备还是先来看看这两个函数吧int devfs_register_chrdev (unsigned int major, const char *name, struct file_operations *fops){ if (boot_options & OPTION_ONLY) return 0; return register_chrdev (major, name, fops);} 这个函数很简单,其实就是调用了register_chrdev来注册设备,而用register_chrdev来注册设备是需要使用mknod来建立节点的,看看register_chrdev函数,其实只是对一个全局的chrdevs结构赋值而已。void tty_register_devfs (struct tty_driver *driver, unsigned int flags, unsigned minor){#ifdef CONFIG_DEVFS_FS umode_t mode = S_IFCHR | S_IRUSR | S_IWUSR; kdev_t device = MKDEV (driver->major, minor); int idx = minor - driver->minor_start; char buf[32];
switch (device) { case TTY_DEV: case PTMX_DEV: mode |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; break; default: if (driver->major == PTY_MASTER_MAJOR) mode |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; break; } if ( (minor < driver->minor_start) || (minor >= driver->minor_start + driver->num) ) { printk(KERN_ERR "Attempt to register invalid minor number " "with devfs (%d:%d)./n", (int)driver->major,(int)minor); return; }# ifdef CONFIG_UNIX98_PTYS if ( (driver->major >= UNIX98_PTY_SLAVE_MAJOR) && (driver->major < UNIX98_PTY_SLAVE_MAJOR + UNIX98_NR_MAJORS) ) flags |= DEVFS_FL_CURRENT_OWNER;# endif sprintf(buf, driver->name, idx + driver->name_base); devfs_register (NULL, buf, flags | DEVFS_FL_DEFAULT, driver->major, minor, mode, &tty_fops, NULL);#endif /* CONFIG_DEVFS_FS */}对于tty_register_devfs函数则需要定义CONFIG_DEVFS_FS宏才有意义,这意味着内核支持devfs设备文件系统。使用devfs_register注册设备同时在/dev建立相应的节点。只要没有使用mknod建立节点,就不会和devfs_register产生冲突,而我实际使用的都是devfs_register注册产生的设备。
关键要考虑的还是tty_fops这个结构中的设备方法static struct file_operations tty_fops = { llseek: no_llseek, read: tty_read, write: tty_write, poll: tty_poll, ioctl: tty_ioctl, open: tty_open, release: tty_release, fasync: tty_fasync,};
看看tty_open方法,在其中能看到对不同设备的处理方法,重点还是看console的 device = inode->i_rdev; if (device == SYSCONS_DEV) { struct console *c = console_drivers; while(c && !c->device) c = c->next; if (!c) return -ENODEV; device = c->device(c); filp->f_flags |= O_NONBLOCK; /* Don't let /dev/console block */ noctty = 1; } retval = init_dev(device, &tty); filp->private_data = tty; if (tty->driver.open) retval = tty->driver.open(tty, filp);console_drivers就是前一片文章介绍过的全局变量,存放着真正控制台ttyS0的信息,在这里并没有把file结构中的f_pos重新赋值,而是让filp->private_data赋值为tty结构然后通过tty->driver.open去打开真正的控制台设备。同时在tty_read,tty_write方法中也是通过这个tty结构实现对真正控制台设备的读写的。如果定义了CONFIG_VT宏,就是make menuconfig,在Character devices中选中Virtual terminal,而且关闭串口控制台,这样就会把显示器还有键盘做为默认的控制台设备,在console drivers下可以看到VGA text console被选中。希望以后可以加上usb键盘和液晶屏作为控制台吧。
