国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

linux kernel下輸入輸出console如何實現

2019-11-09 18:46:03
字體:
來源:轉載
供稿:網友

轉載地址:http://blog.csdn.net/skyflying2012/article/details/41078349

最近工作在調試usb虛擬串口,讓其作為kernel啟動的調試串口,以及user空間的輸入輸出控制臺。

利用這個機會,學習下PRintk如何選擇往哪個console輸出以及user空間下控制臺如何選擇,記錄與此,與大家共享,也方便自己以后翻閱。

Kernel版本號:3.4.55

依照我的思路(還是時間順序)分了4部分,指定kernel調試console ,  kernel下printk console的選擇 ,kernel下console的注冊,user空間console的選擇。

一 指定kernel調試console

首先看kernel啟動時如何獲取和處理指定的console參數。

kernel的啟動參數cmdline可以指定調試console,如指定‘console=ttyS0,115200’,

kernel如何解析cmdline,我之前寫了一篇博文如下:

http://blog.csdn.NET/skyflying2012/article/details/41142801

根據之前的分析,cmdline中有console=xxx,start_kernel中parse_args遍歷.init.setup段所有obs_kernel_param。

kernel/printk.c中注冊了‘console=’的解析函數console_setup(注冊了obs_kernel_param),所以匹配成功,會調用console_setup來解析,如下:

[cpp] view plain copy 在CODE上查看代碼片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);  參數是console=的值字符串,如“ttyS0,115200”,console_setup對console=參數值做解析,以ttyS0,115200為例,最后buf=“ttyS”,idx=0,options="115200",brl_options=NULL。調用__add_preferred_console如下:

[cpp] view%20plain copy /*  * If exclusive_console is non-NULL then only this console is to be printed to.  */  static struct console *exclusive_console;    /*  *  Array of consoles built from command line options (console=)  */  struct console_cmdline  {                        char    name[8];            /* Name of the driver       */      int index;              /* Minor dev. to use        */      char    *options;           /* Options for the driver   */  #ifdef CONFIG_A11Y_BRAILLE_CONSOLE      char    *brl_options;           /* Options for braille driver */  #endif  };    #define MAX_CMDLINECONSOLES 8            static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];  static int selected_console = -1;  static int preferred_console = -1;  int console_set_on_cmdline;  EXPORT_SYMBOL(console_set_on_cmdline);  static int __add_preferred_console(char *name, int idx, char *options,                     char *brl_options)  {      struct console_cmdline *c;      int i;        /*      *  See if this tty is not yet registered, and      *  if we have a slot free.      */      for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)          if (strcmp(console_cmdline[i].name, name) == 0 &&                console_cmdline[i].index == idx) {                  if (!brl_options)                      selected_console = i;                  return 0;          }      if (i == MAX_CMDLINECONSOLES)          return -E2BIG;      if (!brl_options)          selected_console = i;      c = &console_cmdline[i];      strlcpy(c->name, name, sizeof(c->name));      c->options = options;  #ifdef CONFIG_A11Y_BRAILLE_CONSOLE      c->brl_options = brl_options;  #endif      c->index = idx;      return 0;  }  

kernel利用結構體數組console_cmdline[8],最多可支持8個cmdline傳入的console參數。

__add_preferred_console將name%20idx%20options保存到數組下一個成員console_cmdline結構體中,如果數組中已有重名,則不添加,并置selected_console為最新添加的console_cmdline的下標號。

比如cmdline中有“console=ttyS0,115200%20console=ttyS1,9600”

則在console_cmdline[8]數組中console_cmdline[0]代表ttyS0,console_cmdline[1]代表ttyS1,而selected_console=1.

二%20kernel下printk%20console的選擇

kernel下調試信息是通過printk輸出,如果要kernel正常打印,則需要搞明白printk怎么選擇輸出的設備。

關于printk的實現原理,我在剛工作的時候寫過一篇博文,kernel版本是2.6.21的,但是原理還是一致的,可供參考:

http://blog.csdn.Net/skyflying2012/article/details/7970341

printk首先將輸出內容添加到一個kernel緩沖區中,叫log_buf,log_buf相關代碼如下:

[cpp] view%20plain copy #define MAX_CMDLINECONSOLES 8    static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];  static int selected_console = -1;  static int preferred_console = -1;  int console_set_on_cmdline;  EXPORT_SYMBOL(console_set_on_cmdline);    /* Flag: console code may call schedule() */  static int console_may_schedule;    #ifdef CONFIG_PRINTK            static char __log_buf[__LOG_BUF_LEN];  static char *log_buf = __log_buf;  static int log_buf_len = __LOG_BUF_LEN;  static unsigned logged_chars; /* Number of chars produced since last read+clear Operation */  static int saved_console_loglevel = -1;  log_buf的大小由kernel%20menuconfig配置,我配置的CONFIG_LOG_BUF_SHIFT為17,則log_buf為128k。

printk內容會一直存在log_buf中,log_buf滿了之后則會從頭在開始存,覆蓋掉原來的數據。

根據printk的實現原理,printk最后調用console_unlock實現log_buf數據刷出到指定設備。

這里先不關心printk如何處理log%20buf數據(比如添加內容級別),只關心printk如何一步步找到指定的輸出設備,根據printk.c代碼,可以找到如下線索。

printk->vprintk->console_unlock->call_console_drivers->_call_console_drivers->_call_console_drivers->__call_console_drivers

看線索最底層__call_console_drivers代碼。如下:

[cpp] view%20plain copy /*  * Call the console drivers on a range of log_buf  */  static void __call_console_drivers(unsigned start, unsigned end)  {      struct console *con;        for_each_console(con) {          if (exclusive_console && con != exclusive_console)              continue;          if ((con->flags & CON_ENABLED) && con->write &&                  (cpu_online(smp_processor_id()) ||                  (con->flags & CON_ANYTIME)))              con->write(con, &LOG_BUF(start), end - start);      }  }  for_each_console定義如下:

[cpp] view%20plain copy /*            * for_each_console() allows you to iterate on each console  */               #define for_each_console(con) /      for (con = console_drivers; con != NULL; con = con->next)  遍歷console_drivers鏈表所有console%20struct,如果有exclusive_console,則調用與exclusive_console一致console的write,

如果exclusive_console為NULL,則調用所有ENABLE的console的write方法將log%20buf中start到end的內容發出。

可以看出,execlusive_console來指定printk輸出唯一console,如果未指定,則向所有enable的console寫。

默認情況下execlusive_console=NULL,所以printk默認是向所有enable的console寫!

只有一種情況是指定execlusive_console,就是在console注冊時,下面會講到。

到這里就很明了了,kernel下每次printk打印,首先存log_buf,然后遍歷console_drivers,找到合適console(execlusive_console或所有enable的),刷出log。

console_drivers鏈表的成員是哪里來的,誰會指定execulsive_console?接著來看下一部分,kernel下console的注冊

三%20kernel下console的注冊

上面分析可以看出,作為kernel移植最基本的一步,kernel下printk正常輸出,最重要的一點是在console_drivers鏈表中添加console%20struct。那誰來完成這個工作?

答案是register_console函數,在printk.c中,下面來分析下該函數。

[cpp] view%20plain copy void register_console(struct console *newcon)  {      int i;      unsigned long flags;      struct console *bcon = NULL;        //如果注冊的是bootconsole(kernel早期啟動打印),需要檢查console_drivers中      //沒有“real console”也就是說bootconsole必須是第一個注冊的console。      if (console_drivers && newcon->flags & CON_BOOT) {          /* find the last or real console */          for_each_console(bcon) {              if (!(bcon->flags & CON_BOOT)) {                  printk(KERN_INFO "Too late to register bootconsole %s%d/n",                      newcon->name, newcon->index);                  return;              }          }      }        if (console_drivers && console_drivers->flags & CON_BOOT)          bcon = console_drivers;        //preferred console為console_cmdline中最后一個console      if (preferred_console < 0 || bcon || !console_drivers)          preferred_console = selected_console;        if (newcon->early_setup)          newcon->early_setup();        if (preferred_console < 0) {          if (newcon->index < 0)              newcon->index = 0;          if (newcon->setup == NULL ||              newcon->setup(newcon, NULL) == 0) {              newcon->flags |= CON_ENABLED;              if (newcon->device) {                  newcon->flags |= CON_CONSDEV;                  preferred_console = 0;              }          }      }        //檢查newcon是否是cmdline指定的console,如果是,則使能(CON_ENABLE)并初始化該console      for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];              i++) {          if (strcmp(console_cmdline[i].name, newcon->name) != 0)              continue;          if (newcon->index >= 0 &&              newcon->index != console_cmdline[i].index)              continue;          if (newcon->index < 0)              newcon->index = console_cmdline[i].index;  #ifdef CONFIG_A11Y_BRAILLE_CONSOLE          if (console_cmdline[i].brl_options) {              newcon->flags |= CON_BRL;              braille_register_console(newcon,                      console_cmdline[i].index,                      console_cmdline[i].options,                      console_cmdline[i].brl_options);              return;          }  #endif          if (newcon->setup &&              newcon->setup(newcon, console_cmdline[i].options) != 0)              break;          newcon->flags |= CON_ENABLED;          newcon->index = console_cmdline[i].index;          if (i == selected_console) {              //如果newcon是cmdline指定的最新的console,則置位CONSDEV              newcon->flags |= CON_CONSDEV;              preferred_console = selected_console;          }          break;      }        //該console沒有使能,退出      if (!(newcon->flags & CON_ENABLED))          return;        //如果有bootconsole,則newcon不需要輸出register之前的log,因為如果bootconsole和newcon是同一個設備      //則之前的log就輸出2次      if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))          newcon->flags &= ~CON_PRINTBUFFER;        //把newcon加入console_drivers鏈表,對于置位CON_CONSDEV的con,放在鏈表首      console_lock();      if ((newcon->flags & CON_CONSDEV) || console_drivers == NULL) {          newcon->next = console_drivers;          console_drivers = newcon;          if (newcon->next)              newcon->next->flags &= ~CON_CONSDEV;      } else {          newcon->next = console_drivers->next;          console_drivers->next = newcon;      }      if (newcon->flags & CON_PRINTBUFFER) {          //如果newcon置位PRINTBUFFER,則將log全部刷出          raw_spin_lock_irqsave(&logbuf_lock, flags);          con_start = log_start;          raw_spin_unlock_irqrestore(&logbuf_lock, flags);          //修改printk輸出的指定唯一exclusive_console為newcon          //保證將之前的log只輸出到newcon          exclusive_console = newcon;      }      //解鎖console,刷出log到newcon      console_unlock();      console_sysfs_notify();        //如果有bootconsole,則unregister bootconsole(從console_drivers中刪掉)      //并告訴使用者現在console切換      if (bcon &&          ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) &&          !keep_bootcon) {          /* we need to iterate through twice, to make sure we print          * everything out, before we unregister the console(s)          */          printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled/n",              newcon->name, newcon->index);          for_each_console(bcon)              if (bcon->flags & CON_BOOT)                  unregister_console(bcon);      } else {          printk(KERN_INFO "%sconsole [%s%d] enabled/n",              (newcon->flags & CON_BOOT) ? "boot" : "" ,              newcon->name, newcon->index);      }  }  如果之前注冊了bootconsole,則不會將該次register之前的log刷出,防止bootconsole和該次注冊的newcon是同一個物理設備時,log打印2次。

如果沒有bootconsole,則會指定exclusive_console=newcon,console_unlock時,刷新全部log到該指定exclusive%20console。

console_unlock結束時會將exclusive_console置NULL,所以exclusive%20console默認情況下就是NULL。

最后會unregister%20bootconsole,是將bootconsole從console_drivers中刪除,這樣之后的printk就不會想bootconsole輸出了。

有意思的一個地方是,在unregister%20bootconsole之前的printk:

[cpp] view%20plain copy printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled/n",              newcon->name, newcon->index);  因為此時bootconsole還沒刪掉,而newconsole已經加入console_drivers,如果bootconsole和newconsole是同一個物理設備,我們會看到這句printk會出現2次哦!

如果在cmdline指定2個I/O設備,如“console==ttyS0,115200%20console=ttyS1,115200”,因ttyS設備都是serial%20driver中注冊的real%20console,所以會看到kernel的打印分別出現在2個串口上!

boot%20console和real%20console差別在于bootconsole注冊于kernel啟動早期,方便對于kernel早期啟動進行調試打印。

那這些console是在哪里調用register_console進行注冊的?

bootconsole的注冊,如arch/arm/kernel/early_printk.c,是在parse_args參數解析階段注冊bootconsole。

在start_kernel中console_init函數也會遍歷.con_initcall.init段中所有注冊函數,而這些注冊函數也可以來注冊bootconsole。

.con_initcall.init段中函數的注冊可以使用宏定義console_initcall。這些函數中調用register_console,方便在kernel初期實現printk打印。

realconsole的注冊,是在各個driver,如serial加載時完成。

經過上面分析,對于一個新實現的輸入輸出設備,如果要將其作為kernel下的printk調試輸出設備,需要2步:

(1)register%20console,console%20struct如下:

[cpp] view%20plain copy struct console {      char    name[16];      void    (*write)(struct console *, const char *, unsigned);      int (*read)(struct console *, char *, unsigned);      struct tty_driver *(*device)(struct console *, int *);       void    (*unblank)(void);      int (*setup)(struct console *, char *);       int (*early_setup)(void);      short   flags;      short   index;      int cflag;      void    *data;      struct   console *next;  };  定義一個console,因為kernel調試信息是單向的,沒有交互,所以只需要實現write即可,還需要實現setup函數,進行設備初始化(如設置波特率等),以及標志位flags(將所有log刷出),舉個例子,如下:

[cpp] view%20plain copy static struct console u_console =  {      .name       = "ttyS",      .write      = u_console_write,      .setup      = u_console_setup,      .flags      = CON_PRINTBUFFER,      .index      = 0,      .data       = &u_reg,  };static int __init  u_console_init(void)  {      register_console(&u_console);      return 0;  }  

為了調試方便,可以在console_init調用該函數進行注冊,則需要

[cpp] view%20plain copy console_initcall(u_console_init);  也可以在kernel加載driver時調用,則需要在driver的probe時調用u_console_init,但是這樣只能等driver調register_console之后,console_unlock才將所有log刷出,之前的log都會存在log%20buf中。

(2)cmdline指定調試console,在kernel的cmdline添加參數console=ttyS0,115200

四%20user空間console的選擇

用戶空間的輸入輸出依賴于其控制臺使用的哪個,這里有很多名詞,如控制臺,tty,console等,這些名字我也很暈,不用管他們的真正含義,搞嵌入式,直接找到它的實現,搞明白從最上層軟件,到最底層硬件,如何操作,還有什么會不清楚呢。

在start_kernel中最后起內核init進程時,如下:

[cpp] view%20plain copy /* 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");        (void) sys_dup(0);      (void) sys_dup(0);  去打開console設備,console設備做了控制臺。

console設備文件的創建在driver/tty/tty_io.c中,如下:

[cpp] view%20plain copy 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");        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;  }  

console的操作函數都是使用的tty的操作函數,看open的實現,如何找到具體的操作設備:

[cpp] view%20plain copy 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,如下:

[cpp] view%20plain copy static struct tty_driver *tty_lookup_driver(dev_t device, struct file *filp,          int *noctty, int *index)  {      struct tty_driver *driver;        switch (device) {  #ifdef CONFIG_VT      case MKDEV(TTY_MAJOR, 0): {          extern struct tty_driver *console_driver;          driver = tty_driver_kref_get(console_driver);          *index = fg_console;          *noctty = 1;          break;      }  #endif      case MKDEV(TTYAUX_MAJOR, 1): {          struct tty_driver *console_driver = console_device(index);          if (console_driver) {              driver = tty_driver_kref_get(console_driver);              if (driver) {                  /* Don't let /dev/console block */                  filp->f_flags |= O_NONBLOCK;                  *noctty = 1;                  break;              }          }          return ERR_PTR(-ENODEV);      }      default:          driver = get_tty_driver(device, index);          if (!driver)              return ERR_PTR(-ENODEV);          break;      }      return driver;  }  console設備文件,次設備號是1,根據代碼,會調用console_device來獲取對應的tty_driver,如下:

[cpp] view%20plain copy 派生到我的代碼片struct tty_driver *console_device(int *index)  {             struct console *c;      struct tty_driver *driver = NULL;        console_lock();       for_each_console(c) {          if (!c->device)              continue;           driver = c->device(c, index);          if (driver)              break;      }         console_unlock();      return driver;  }  又遇到了熟悉的for_each_console,遍歷console_drivers鏈表,對于存在device成員的console,調用device方法,獲取tty_driver,退出遍歷。

之后對于該console設備的讀寫操作都是基于該tty_driver。

所有的輸入輸出設備都會注冊tty_driver。

所以,對于一個新實現的輸入輸出設備,如果想讓其即作為kernel的printk輸出設備,也作為user空間的控制臺,則需要在上面u_console基礎上再實現device方法成員,來返回該設備的tty_driver。

那么還有一個問題:

如果cmdline指定2個I/O設備,“console=ttyS0,115200 console=ttyS1,115200”,user空間選擇哪個作為console?

用戶空間console open時,console_device遍歷console_drivers,找到有device成員的console,獲取tty_driver,就會退出遍歷。

所以哪個console放在console_drivers前面,就會被選擇為user空間的console。

在分析register_console時,如果要注冊的newcon是cmdline指定的最新的console(i = selected_console),則置位CON_CONSDEV,

而在后面newcon加入console_drivers時,判斷該置位,置位CON_CONSDEV,則將newcon加入到console_drivers的鏈表頭,否則插入到后面。

所以這里user空間會選擇ttyS1作為用戶控件的console!

總結下,kernel和user空間下都有一個console,關系到kernel下printk的方向和user下printf的方向,實現差別還是很大的。

kernel下的console是輸入輸出設備driver中實現的簡單的輸出console,只實現write函數,并且是直接輸出到設備。

user空間下的console,實際就是tty的一個例子,所有操作函數都繼承與tty,全功能,可以打開 讀寫 關閉,所以對于console的讀寫,都是由kernel的tty層來最終發送到設備。

kernel的tty層之下還有ldisc線路規程層,線路規程層之下才是具體設備的driver。

ldisc層處理一些對于控制臺來說有意義的輸入輸出字符,比如輸入的crtl+C,輸出的‘/n‘進過線路規程會變為’/n/r‘。

所以對于kernel下console的write方法,不要忘記,對于log buf中'/n'的處理,實現一個簡單的線路規程!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 临城县| 凭祥市| 化州市| 滦平县| 宁明县| 景宁| 定兴县| 牡丹江市| 黔南| 开封市| 衡水市| 江北区| 辰溪县| 通辽市| 广饶县| 巴中市| 砚山县| 洛浦县| 原平市| 青浦区| 哈巴河县| 永清县| 甘孜县| 安徽省| 新化县| 容城县| 广饶县| 盐津县| 杭州市| 西昌市| 铜山县| 玉环县| 天长市| 新泰市| 合山市| 安义县| 定兴县| 巢湖市| 南木林县| 伊宁市| 侯马市|