首先在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=0
consle 设备 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键盘和液晶屏作为控制台吧。