免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1467 | 回复: 1
打印 上一主题 下一主题

嵌入式系统终端分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-12-21 17:15 |只看该作者 |倒序浏览


嵌入式系统终端分析
------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://sjj0412.cublog.cn/
------------------------------------------  
当我们打开机器或一个嵌入式系统时,我们可能都适应了它会显示信息,我们也理所当然的认为这是应该,但是它从计算机中通过多少关口才到达显示器或串口终端显示的呢,估计大家都没在意过,其实这个过程在linux操作系统中可为千辛万库啊,当然在其他操作系统也一样,今天我就来分析从printf到显示设备的过程。
  我们先来看下面这些程序。
1  If (sys_open((const char __user *) "/dev/console", O_RDWR, 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/tty1,echo”kfdl”>/dev/console这三调命令都可以在屏幕下看到信息,这说明这三个设备文件都引导到了同一个tty_driver,tty_struct,
然后按ctrl+alt+f2,进入另一个控制,
echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty2,echo”kfdl”>/dev/console,仍然可以看到信息,但输入/dev/tty1输出不了信息,为什么,因为这个是控制台2了啊,而console有机制可以指向活动的tty,所以可以显示,当然/dev/tty0本身就是指向活动的控制台的。
在arm嵌入式系统中,
我们也可以实验,但是结果很不一样,
echo “kfdl”>/dev/tty0,echo “kfds”>/dev/tty1,echo”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);//console的tty找到了啊
         if (driver)
                break;
  }
  release_console_sem();
  return driver;
}
由上面的红色可知,console设备的driver由console_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);
  }
………
………
}
由上可知在Pc中console和tty0~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
         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_driver的console_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
         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 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
         put_tty_driver(normal);
         kfree(drv->state);
     }
     return retval;
}

从上面可以看到,当串口注册时它作为一般的tty_driver注册,但是由于uart_driver还是console.data,且在console设备通过console_device然后uart_driver->device函数会把uart_driver的tty_driver(上面的normal)赋给console故它还是console的tty_driver,这个可以明白,串口我们当然也希望当作一般的串口接口,而不仅仅是作为控制终端在超级终端中显示的。
无论是一般tty_driver,还是console的tty_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
         (*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_add,register_chrdev()等添加
的,而/dev/tty~ttyn是在vty_init中通过tty_driver_register创建的,并且创建console_driver->num即MAX_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(由于这个device的dev_t由它的tty_driver major指定,当打开这个设备肯定可以找到这个驱动)创建终端的设备,如果这个些串口终端设备的uart_driver->con存在且con.flags=con_enable,它也同时console_register,所以这时console可以指向它们中的任意一个。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/96265/showart_2127252.html

论坛徽章:
0
2 [报告]
发表于 2009-12-24 10:51 |只看该作者
写得很详细,收获不小,多谢
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP