zoukankan      html  css  js  c++  java
  • linux kernel下输入输出console如何实现【转】

    转自:https://www.cnblogs.com/huan-huan/p/8493063.html

    最近工作在调试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来解析,如下:
    
     
    
    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=
    ");  
                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如下:
    
     
    
    /* 
     * 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 idx options保存到数组下一个成员console_cmdline结构体中,如果数组中已有重名,则不添加,并置selected_console为最新添加的console_cmdline的下标号。
    
    比如cmdline中有“console=ttyS0,115200 console=ttyS1,9600”
    
    则在console_cmdline[8]数组中console_cmdline[0]代表ttyS0,console_cmdline[1]代表ttyS1,而selected_console=1.
    
     
    
    二 kernel下printk console的选择
    
    kernel下调试信息是通过printk输出,如果要kernel正常打印,则需要搞明白printk怎么选择输出的设备。
    
    关于printk的实现原理,我在刚工作的时候写过一篇博文,kernel版本是2.6.21的,但是原理还是一致的,可供参考:
    
    http://blog.csdn.net/skyflying2012/article/details/7970341
    
    printk首先将输出内容添加到一个kernel缓冲区中,叫log_buf,log_buf相关代码如下:
    
     
    
    #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 menuconfig配置,我配置的CONFIG_LOG_BUF_SHIFT为17,则log_buf为128k。
    
     
    
    printk内容会一直存在log_buf中,log_buf满了之后则会从头在开始存,覆盖掉原来的数据。
    
    根据printk的实现原理,printk最后调用console_unlock实现log_buf数据刷出到指定设备。
    
    这里先不关心printk如何处理log buf数据(比如添加内容级别),只关心printk如何一步步找到指定的输出设备,根据printk.c代码,可以找到如下线索。
    
    printk->vprintk->console_unlock->call_console_drivers->_call_console_drivers->_call_console_drivers->__call_console_drivers
    
    看线索最底层__call_console_drivers代码。如下:
    
     
    
    /* 
     * 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定义如下:
    
     
    
    /*           
     * 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 struct,如果有exclusive_console,则调用与exclusive_console一致console的write,
    
    如果exclusive_console为NULL,则调用所有ENABLE的console的write方法将log buf中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的注册
    
     
    
    三 kernel下console的注册
    
    上面分析可以看出,作为kernel移植最基本的一步,kernel下printk正常输出,最重要的一点是在console_drivers链表中添加console struct。那谁来完成这个工作?
    
    答案是register_console函数,在printk.c中,下面来分析下该函数。
    
     
    
    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
    ",  
                        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
    ",  
                newcon->name, newcon->index);  
            for_each_console(bcon)  
                if (bcon->flags & CON_BOOT)  
                    unregister_console(bcon);  
        } else {  
            printk(KERN_INFO "%sconsole [%s%d] enabled
    ",  
                (newcon->flags & CON_BOOT) ? "boot" : "" ,  
                newcon->name, newcon->index);  
        }  
    }  
    如果之前注册了bootconsole,则不会将该次register之前的log刷出,防止bootconsole和该次注册的newcon是同一个物理设备时,log打印2次。
    
    如果没有bootconsole,则会指定exclusive_console=newcon,console_unlock时,刷新全部log到该指定exclusive console。
    
    console_unlock结束时会将exclusive_console置NULL,所以exclusive console默认情况下就是NULL。
    
    最后会unregister bootconsole,是将bootconsole从console_drivers中删除,这样之后的printk就不会想bootconsole输出了。
    
    有意思的一个地方是,在unregister bootconsole之前的printk:
    
     
    
    printk(KERN_INFO "console [%s%d] enabled, bootconsole disabled
    ",  
                newcon->name, newcon->index);  
    因为此时bootconsole还没删掉,而newconsole已经加入console_drivers,如果bootconsole和newconsole是同一个物理设备,我们会看到这句printk会出现2次哦!
    
    
     
    
    如果在cmdline指定2个I/O设备,如“console==ttyS0,115200 console=ttyS1,115200”,因ttyS设备都是serial driver中注册的real console,所以会看到kernel的打印分别出现在2个串口上!
    
    boot console和real console差别在于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 console,console struct如下:
    
     
    
    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刷出),举个例子,如下:
    
     
    
     
    
    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调用该函数进行注册,则需要
    
     
    
    console_initcall(u_console_init);  
    也可以在kernel加载driver时调用,则需要在driver的probe时调用u_console_init,但是这样只能等driver调register_console之后,console_unlock才将所有log刷出,之前的log都会存在log buf中。
    
     
    
    (2)cmdline指定调试console,在kernel的cmdline添加参数console=ttyS0,115200
    
    
    
    四 user空间console的选择
    
    用户空间的输入输出依赖于其控制台使用的哪个,这里有很多名词,如控制台,tty,console等,这些名字我也很晕,不用管他们的真正含义,搞嵌入式,直接找到它的实现,搞明白从最上层软件,到最底层硬件,如何操作,还有什么会不清楚呢。
    
    在start_kernel中最后起内核init进程时,如下:
    
     
    
    /* 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.
    ");  
      
        (void) sys_dup(0);  
        (void) sys_dup(0);  
    去打开console设备,console设备做了控制台。
    
     
    
    console设备文件的创建在driver/tty/tty_io.c中,如下:
    
     
    
    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
    ");  
        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
    ");  
        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的实现,如何找到具体的操作设备:
    
     
    
     
    
    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,如下:
    
     
    
    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,如下:
    
     
    
     
    
    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,输出的‘
    ‘进过线路规程会变为’
    
    ‘。
    
     
    
    所以对于kernel下console的write方法,不要忘记,对于log buf中'
    '的处理,实现一个简单的线路规程!
    
     嵌入式全新视频:www.makeru.com.cn/?t=12
    
    嵌入式学习交流群:561213221
    【作者】张昺华
    【大饼教你学系列】https://edu.csdn.net/course/detail/10393
    【新浪微博】 张昺华--sky
    【twitter】 @sky2030_
    【微信公众号】 张昺华
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    死循环(endless loop)
    while循环和do-while循环语句
    99乘法表(for循环嵌套)
    代码块和作用域
    Break语句和Continue语句
    习题两则的简化(利用for循环)
    for循环语句
    条件判断语句(if-else)
    习题两则(自增操作符和数据类型相关知识点)
    字符集、编码和字符串
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/15153150.html
Copyright © 2011-2022 走看看