Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3010452
  • 博文数量: 674
  • 博客积分: 17881
  • 博客等级: 上将
  • 技术积分: 4849
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-17 10:15
文章分类

全部博文(674)

文章存档

2013年(34)

2012年(146)

2011年(197)

2010年(297)

分类: LINUX

2010-06-06 07:48:19

当我们打开机器或一个嵌入式系统时,我们可能都适应了它会显示信息,我们也理所当然的认为这是应该,但是它从计算机中通过多少关口才到达显示器或串口终端显示的呢,估计大家都没在意过,其实这个过程在linux操作系统中可为千辛万库啊,当然在其他操作系统也一样,今天我就来分析从printf到显示设备的过程。

  我们先来看下面这些程序。

1  If (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)

              printk(KERN_WARNING "Warning: unable to open an initial console.\n");

 

2  (void) sys_dup(0);

3  (void) sys_dup(0);   

1语句执行后生成第一个文件,这个文件的文件号为0,第2语句过后产生第二个文件,这个文件的文件号为1,第3语句过后产生第三个文件,这个文件的文件号为2,这时这三个文件都指向同一个文件,那就是设备文件/dev/console,这个三个文件就是大名鼎鼎的标准输入,标准输出,错误输出文件,当然他们有不同属性,后来才改,以后对这些文件号的操作就是对/dev/console的操作,由于所有进程是由这个进程fork继承而来,故所有进程都拥有这些文件号,都可以执行写读操作,即向显示设备输出内容,输入设备读入内容。

在上面语句没有执行前都是通过printk来输出字符,printk是不通过文件系统输出字符的

,而直接通过相关驱动输出到显示设备,执行这些语句后,大家就可以通过向0号文件写入或读入内容(write(0,buf,len))来达到输出字符的目的,也许你会说我们没有用这个write函数啊,其实是操作系统帮你调用这个函数的,也许大家经常在应用程序用printf,这个函数就会调用系统调用write(0,buf,len)等等。

应用层分析就到此位置,但是设备层又来了。

当我们向/dev/console设备文件写或读入数据,怎么就在显示设备上(没有在串口中)显示了,并且在Pc上它是在显示器上显示,在arm等嵌入式设备上,往往是在串口中显示(当然要超级终端或minicom),难道/dev/console很智能,对它确实很智能,要分析这个过程,就不得不谈到终端的概念。

1.      一般终端

2.      控制台终端

3        pts

控制台终端是终端的一种,如/dev/tty0,/dev/tty1…./dev/tty8等虚拟控制台终端,其中/dev/tty0叫当前控制台终端,它通常指向/dev/tty1,…../dev/tty8中的一个,/dev/tty是进程的终端,它可以是控制台终端也可以是其他终端,反正它就是进程拥有的终端。

现在开始非常重要的终端到了,那就是/dev/console有很多人将它说成是外接控制台终端,我不理解,我认为它就是用来输出信息(应用程序printf使用)的终端。我们知道

 

pc中我们的控制台就是/tty1,……/tty..,我们应该记得ctrl+alt+fn来切换控制台吧,这个时候这些控制台就是对应/dev/tty1……/dev/tty4,这个时候这些控制台就是用来输出信息的,照这样说来,/dev/console应该是指向/dev/tty*中的一个,事实上是对的,我们可以在Linux文本模式下实验,在第一个控制台,我们输入

echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty1echo”kfdl”>/dev/console这三调命令都可以在屏幕下看到信息,这说明这三个设备文件都引导到了同一个tty_drivertty_struct,

然后按ctrl+alt+f2,进入另一个控制,

echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty2echo”kfdl”>/dev/console,仍然可以看到信息,但输入/dev/tty1输出不了信息,为什么,因为这个是控制台2了啊,而console有机制可以指向活动的tty,所以可以显示,当然/dev/tty0本身就是指向活动的控制台的。

arm嵌入式系统中,

我们也可以实验,但是结果很不一样,

echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty1echo”kfdl”>/dev/console

中只有echo”kfdl”>/dev/console能输出信息,这是为什么因为在arm的嵌入式系统中,显示信息的不是显示器了,是串口了,所以由于console是引导向显示信息的设备,而

Tty1-tty4只是控制台(只不过在Pc中恰好就是显示器作为显示它才能显示),故只有console,当然如果有显示器等显示设别,echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty1可以在显示器等设备显示,但在串口是如何都不能显示的。

这时如何实现的,我们知道当我们打开终端设备时,内核的入口就是

Tty设备文件打开关键是找到tty_driver,因为它里面有具体tty的操作函数啊。

static int tty_open(struct inode *inode, struct file *filp)

{

         struct tty_struct *tty;

         int noctty, retval;

         struct tty_driver *driver;

         int index;

         dev_t device = inode->i_rdev;

         unsigned short saved_flags = filp->f_flags;

 

         nonseekable_open(inode, filp);

 

retry_open:

         //O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端

 

         //noctty:需不需要更改当前进程的控制终端

         noctty = filp->f_flags & O_NOCTTY;

         index  = -1;

         retval = 0;

 

         mutex_lock(&tty_mutex);

 

         //设备号(5,0) /dev/tty.表示当前进程的控制终端

         if (device == MKDEV(TTYAUX_MAJOR, 0)) {//tty设备文件的driver的获取方式

                   tty = get_current_tty();/这就是为什么说tty代表当前进程终端,代码中就是这样实现的啊

                   //如果当前进程的控制终端不存在,退出

                   if (!tty) {

                            mutex_unlock(&tty_mutex);

                            return -ENXIO;

                   }

 

                   //取得当前进程的tty_driver

                   driver = tty->driver;

                   index = tty->index;

                   filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */

                   /* noctty = 1; */

                   goto got_driver;

         }

#ifdef CONFIG_VT

         //设备号(4,0)./dev/tty0:表示当前的控制台

         if (device == MKDEV(TTY_MAJOR, 0)) {

                   extern struct tty_driver *console_driver;//tty0设备文件的driver的获取方式

                   driver = console_driver;

                   //fg_console: 表示当前的控制台全局变量(console_driver ,fg-console)设置了它为活动控制台

                   index = fg_console;

                   noctty = 1;

                   goto got_driver;

         }

#endif

         //设备号(5,1)./dev/console.表示外接的控制台. 通过regesit_console()

         if (device == MKDEV(TTYAUX_MAJOR, 1)) {

                   driver = console_device(&index);//关键在这里/console设备文件的driver的获取方式

                   if (driver) {

                            /* Don't let /dev/console block */

                            filp->f_flags |= O_NONBLOCK;

                            noctty = 1;

                            goto got_driver;

                   }

                   mutex_unlock(&tty_mutex);

                   return -ENODEV;

         }

 driver = get_tty_driver(device, &index);//tty1~ttyn设备文件driver获取的方式

       ……….

………….

         return 0;

}

 

console_device致关重要啊:

struct tty_driver *console_device(int *index)

{

  struct console *c;

  struct tty_driver *driver = NULL;

 

  acquire_console_sem();

  for (c = console_drivers; c != NULL; c = c->next) {

         if (!c->device)

                continue;

         driver = c->device(c, index);//consoletty找到了啊

         if (driver)

                break;

  }

  release_console_sem();

  return driver;

}

由上面的红色可知,console设备的driverconsole_drivers获取。

console_drivers是如何赋值的,

它是通过register_console 注册的。

void register_console(struct console * console)

{

………………………

………………………..

  if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {

         console->next = console_drivers;

         console_drivers = console;

         if (console->next)

                console->next->flags &= ~CON_CONSDEV;

  } else {

         console->next = console_drivers->next;

         console_drivers->next = console;//这些就将console挂到console_drivers链上了啊

  }

  if (console->flags & CON_PRINTBUFFER) {

         /*

          * release_console_sem() will print out the buffered messages

          * for us.

          */

         spin_lock_irqsave(&logbuf_lock, flags);

         con_start = log_start;

         spin_unlock_irqrestore(&logbuf_lock, flags);

  }

………

………

}

由上可知在Pcconsoletty0~tty1显示是一样的,那它的驱动应该是一样的,也就是说console_driver->device返回的tty_driver应该是console_driver,是如何实现,这个就是con_init实现的哦,

static int __init con_init(void)

{

  const char *display_desc = NULL;

  struct vc_data *vc;

  unsigned int currcons = 0;

 

  acquire_console_sem();

 

  if (conswitchp)

         display_desc = conswitchp->con_startup();

  if (!display_desc) {

         fg_console = 0;

         release_console_sem();

         return 0;

  }

 

  init_timer(&console_timer);

  console_timer.function = blank_screen_t;

  if (blankinterval) {

         blank_state = blank_normal_wait;

         mod_timer(&console_timer, jiffies + blankinterval);

  }

 

  /*

   * kmalloc is not running yet - we use the bootmem allocator.

   */

  for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {

         vc_cons[currcons].d = vc = alloc_bootmem(sizeof(struct vc_data));

         visual_init(vc, currcons, 1);

         vc->vc_screenbuf = (unsigned short *)alloc_bootmem(vc->vc_screenbuf_size);

         vc->vc_kmalloced = 0;

         vc_init(vc, vc->vc_rows, vc->vc_cols,

                currcons || !vc->vc_sw->con_save_screen);

  }

  currcons = fg_console = 0;

  master_display_fg = vc = vc_cons[currcons].d;

  set_origin(vc);

  save_screen(vc);

  gotoxy(vc, vc->vc_x, vc->vc_y);

  csi_J(vc, 0);

  update_screen(vc);

  printk("Console: %s %s %dx%d",

         vc->vc_can_do_color ? "colour" : "mono",

         display_desc, vc->vc_cols, vc->vc_rows);

  printable = 1;

  printk("\n");

 

  release_console_sem();

 

#ifdef CONFIG_VT_CONSOLE

  register_console(&vt_console_driver);//这一句致关重要,它注册了console,挂到了console_driver

#endif

  return 0;

}

再看下vt_console_driverconsole_device是不是返回console_driver,

static struct console vt_console_driver = {

  .name            = "tty",

  .write             = vt_console_print,

  .device          = vt_console_device,

  .unblank = unblank_screen,

  .flags             = CON_PRINTBUFFER,

  .index            = -1,

};

static struct tty_driver *vt_console_device(struct console *c, int *index)

{

  *index = c->index ? c->index-1 : fg_console;

  return console_driver;

}

console_initcall(con_init);

正是,大功告成。

下面来看在arm等嵌入式系统中,console是和串口相关练的,那么应该也有一个类似的注册console的函数,经发现,正是啊

s3c2410.c

tatic int s3c24xx_serial_initconsole(void)

{

  struct s3c24xx_uart_info *info;

  struct platform_device *dev = s3c24xx_uart_devs[0];

 

  dbg("s3c24xx_serial_initconsole\n");

 

  /* select driver based on the cpu */

 

  if (dev == NULL) {

         printk(KERN_ERR "s3c24xx: no devices for console init\n");

         return 0;

  }

 

  if (strcmp(dev->name, "s3c2400-uart") == 0) {

         info = s3c2400_uart_inf_at;

  } else if (strcmp(dev->name, "s3c2410-uart") == 0) {

         info = s3c2410_uart_inf_at;

  } else if (strcmp(dev->name, "s3c2440-uart") == 0) {

         info = s3c2440_uart_inf_at;

  } else {

         printk(KERN_ERR "s3c24xx: no driver for %s\n", dev->name);

         return 0;

  }

 

  if (info == NULL) {

         printk(KERN_ERR "s3c24xx: no driver for console\n");

         return 0;

  }

 

  s3c24xx_serial_console.data = &s3c24xx_uart_drv;

  s3c24xx_serial_init_ports(info);

 

  register_console(&s3c24xx_serial_console);

  return 0;

}

static struct console s3c24xx_serial_console =

{

  .name            = S3C24XX_SERIAL_NAME,

  .device          = uart_console_device,

  .flags             = CON_PRINTBUFFER,

  .index            = -1,

  .write             = s3c24xx_serial_console_write,

  .setup            = s3c24xx_serial_console_setup

};

struct tty_driver *uart_console_device(struct console *co, int *index)

{

  struct uart_driver *p = co->data;

  *index = co->index;

  return p->tty_driver;

}

我们可以知道,返回的是s3c24xx_uart_drv中的tty_dirver,这个driver是何时注册的呢,

其实这就关系到uart结构

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;

  }

 

  s3c2400_serial_init();

  s3c2410_serial_init();

  s3c2440_serial_init();

 

  return 0;

}

int uart_register_driver(struct uart_driver *drv)

{

     struct tty_driver *normal = NULL;

     int i, retval;

 

     BUG_ON(drv->state);

 

     /*

      * Maybe we should be using a slab cache for this, especially if

      * we have a large number of ports to handle.

      */

     drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

     retval = -ENOMEM;

     if (!drv->state)

         goto out;

 

     normal  = alloc_tty_driver(drv->nr);

     if (!normal)

         goto out;

 

     drv->tty_driver = normal;

 

     normal->owner      = drv->owner;

     normal->driver_name    = drv->driver_name;

     normal->name       = drv->dev_name;

     normal->major      = drv->major;

     normal->minor_start    = drv->minor;

     normal->type       = TTY_DRIVER_TYPE_SERIAL;

     normal->subtype        = SERIAL_TYPE_NORMAL;

     normal->init_termios   = tty_std_termios;

     normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;

     normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;

     normal->flags      = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;

     normal->driver_state    = drv;

     tty_set_operations(normal, &uart_ops);

 

     /*

      * Initialise the UART state(s).

      */

     for (i = 0; i < drv->nr; i++) {

         struct uart_state *state = drv->state + i;

 

         state->close_delay     = 500;    /* .5 seconds */

         state->closing_wait    = 30000;  /* 30 seconds */

 

         mutex_init(&state->mutex);

     }

 

     retval = tty_register_driver(normal);

 out:

     if (retval < 0) {

         put_tty_driver(normal);

         kfree(drv->state);

     }

     return retval;

}

 

从上面可以看到,当串口注册时它作为一般的tty_driver注册,但是由于uart_driver还是console.data,且在console设备通过console_device然后uart_driver->device函数会把uart_drivertty_driver(上面的normal)赋给console它还是consoletty_driver,这个可以明白,串口我们当然也希望当作一般的串口接口,而不仅仅是作为控制终端在超级终端中显示的。

无论是一般tty_driver,还是consoletty_driver,它的统一接口是uart_opd,这就区别了其他tty_driver

Tty_driver->driver_data=uart_driver

Uart_open

Tty_struct->driver_data=uart_state;

 

s3c24xx_serial_initconsole,和con_init又是在那调用的呢在start_kernel->console_init调用的

all = __con_initcall_start;

  while (call < __con_initcall_end) {

         (*call)();

         call++;

  }

__con_initcall_start是一个初始段,通过console_initcall注册,

#define console_initcall(fn) \

  static initcall_t __initcall_##fn \

  __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

Console_initcall(con_init)

console_initcall(s3c24xx_serial_initconsole);

 

下面讲/dev/tty,/dev/tty0,/dev/console,串口终端/tts/0~n(在内核叫做ttySAC0~n)的产生过程,虚拟控制台终端/dev/tty1~ttyn

/dev/tty/dev/tty0,/dev/console都是在tty_init中通过cdev_addregister_chrdev()等添加

的,而/dev/tty~ttyn是在vty_init中通过tty_driver_register创建的,并且创建console_driver->numMAX_NR_CONSOLES,在alloc_ttydriver是赋值的,而/tts/0~n是在s3c24xx_serial_probe中,当static inline int s3c2440_serial_init(void)

{

  return s3c24xx_serial_init(&s3c2440_serial_drv, &s3c2440_uart_inf);

}

s3c2440_serial_drv检测到串口设备时就会调用s3c24xx_serial_probe,然后它就会通过uart_add_one_port->tty_register_device(由于这个devicedev_t由它的tty_driver major指定,当打开这个设备肯定可以找到这个驱动)创建终端的设备,如果这个些串口终端设备的uart_driver->con存在且con.flags=con_enable,它也同时console_register,所以这时console可以指向它们中的任意一个。

阅读(876) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~