- 论坛徽章:
- 0
|
一:前言我们在之前分析过input子系统和tty设备驱动架构.今天需要将两者结合起来.看看linux中的控制台是怎么样实现的.二:控制台驱动的初始化之前在分析tty驱动架构的时候曾分析到.主设备为4,次设备为0的设备节点,即/dev/tty0为当前的控制终端.有tty_init()中,有以下代码段:static int __init tty_init(void){ …… …… #ifdef CONFIG_VT cdev_init(&vc0_cdev, &console_fops); if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) || register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") panic("Couldn't register /dev/tty0 driver\n"); device_create(tty_class, NULL, MKDEV(TTY_MAJOR, 0), "tty0"); vty_init();#endif return 0;}CONFIG_VT:是指配置虚拟终端.即我们所说的控制台.在此可以看到TTY_MAJOR(4),0对应的设备节点操作集为console_fops.继续跟进vty_init()int __init vty_init(void){ vcs_init(); console_driver = alloc_tty_driver(MAX_NR_CONSOLES); if (!console_driver) panic("Couldn't allocate console driver\n"); console_driver->owner = THIS_MODULE; console_driver->name = "tty"; console_driver->name_base = 1; console_driver->major = TTY_MAJOR; console_driver->minor_start = 1; console_driver->type = TTY_DRIVER_TYPE_CONSOLE; console_driver->init_termios = tty_std_termios; console_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS; tty_set_operations(console_driver, &con_ops); if (tty_register_driver(console_driver)) panic("Couldn't register console driver\n"); kbd_init(); console_map_init();#ifdef CONFIG_PROM_CONSOLE prom_con_init();#endif#ifdef CONFIG_MDA_CONSOLE mda_console_init();#endif return 0;}经过我们之前的tty驱动架构分析,这段代码看起来就比较简单了,它就是注册了一个tty驱动.这个驱动对应的操作集是位于con_ops里面的.仔
细看.在之后还会调用kbd_init().顾名思义,这个是一个有关键盘的初始化.控制终端跟键盘有什么关系呢?在之前分析tty的时候,曾提到过,.
对于控制台而言,它的输入设备是键盘鼠标,它的输出设备是当前显示器.这两者是怎么关联起来的呢?不着急.请看下面的分析. 三:控制台的open操作在前面分析了,对应console的操作集为con_ops.定义如下:static const struct file_operations console_fops = { .llseek = no_llseek, .read = tty_read, .write = redirected_tty_write, .poll = tty_poll, .ioctl = tty_ioctl, .compat_ioctl = tty_compat_ioctl, .open = tty_open, .release = tty_release, .fasync = tty_fasync,};里面的函数指针值我们都不陌生了,在之前分析的tty驱动中已经分析过了.结合前面的tty驱动分析.我们知道在open的时候,会调用ldisc的open和tty_driver.open.对于ldisc默认是tty_ldiscs[0].我们来看下它的具体赋值.console_init():void __init console_init(void){ initcall_t *call; /* Setup the default TTY line discipline. */ (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY); /* * set up the console device so that later boot sequences can * inform about problems etc.. */ call = __con_initcall_start; while (call (*call)(); call++; }}在这里,通过tty_register_ldisc.将tty_ldisc_N_TTY注册为了第N_TTY项.即第1项. tty_ldisc_N_TTY定义如下:struct tty_ldisc tty_ldisc_N_TTY = { .magic = TTY_LDISC_MAGIC, .name = "n_tty", .open = n_tty_open, .close = n_tty_close, .flush_buffer = n_tty_flush_buffer, .chars_in_buffer = n_tty_chars_in_buffer, .read = read_chan, .write = write_chan, .ioctl = n_tty_ioctl, .set_termios = n_tty_set_termios, .poll = normal_poll, .receive_buf = n_tty_receive_buf, .write_wakeup = n_tty_write_wakeup}对应的open操作为n_tty_open:static int n_tty_open(struct tty_struct *tty){ if (!tty) return -EINVAL; /* This one is ugly. Currently a malloc failure here can panic */ if (!tty->read_buf) { tty->read_buf = alloc_buf(); if (!tty->read_buf) return -ENOMEM; } memset(tty->read_buf, 0, N_TTY_BUF_SIZE); reset_._flags(tty); tty->column = 0; n_tty_set_termios(tty, NULL); tty->minimum_to_wake = 1; tty->closing = 0; return 0;}它为tty->read_buf分配内存.这个buffer空间大小为N_TTY_BUF_SIZE.read_buf实际上就是从按键的缓存区.然后调用reset_flags()来初始化tty中的一些字段:static void reset_buffer_flags(struct tty_struct *tty){ unsigned long flags; spin_lock_irqsave(&tty->read_lock, flags); tty->read_head = tty->read_tail = tty->read_cnt = 0; spin_unlock_irqrestore(&tty->read_lock, flags); tty->canon_head = tty->canon_data = tty->erasing = 0; memset(&tty->read_flags, 0, sizeof tty->read_flags); n_tty_set_room(tty); check_unthrottle(tty);}这里比较简,不再详细分析.在这里要注意几个tty成员的含义:Tty->read_head, tty->read_tail , tty->read_cnt分别代表read_buf中数据的写入位置,读取位置和数据总数.read_buf是一个环形缓存区.n_tty_set_room()是设备read_buf中的可用缓存区check_unthrottle():是用来判断是否需要打开”阀门”,允许输入数据流入 对于console tty_driver对应的open函数如下示:static int con_open(struct tty_struct *tty, struct file *filp){ unsigned int currcons = tty->index; int ret = 0; acquire_console_sem(); if (tty->driver_data == NULL) { ret = vc_allocate(currcons); if (ret == 0) { struct vc_data *vc = vc_cons[currcons].d; tty->driver_data = vc; vc->vc_tty = tty; if (!tty->winsize.ws_row && !tty->winsize.ws_col) { tty->winsize.ws_row = vc_cons[currcons].d->vc_rows; tty->winsize.ws_col = vc_cons[currcons].d->vc_cols; } release_console_sem(); vcs_make_sysfs(tty); return ret; } } release_console_sem(); return ret;}tty->index表示的是tty_driver所对示的设备节点序号.在这里也就是控制台的序列.用alt+fn就可以切换控制终端.在这里,它主要为vc_cons[ ]数组中的对应项赋值.并将tty和vc建立关联. 四:控制台的read操作从tty驱动架构中分析可得到,最终的read操作会转入到ldsic->read中进行.相应tty_ldisc_N_TTY的read操作如下.这个函数代码较长,分段分析如下:static ssize_t read_chan(struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr){ unsigned char __user *b = buf; DECLARE_WAITQUEUE(wait, current); int c; int minimum, time; ssize_t retval = 0; ssize_t size; long timeout; unsigned long flags; do_it_again: if (!tty->read_buf) { printk(KERN_ERR "n_tty_read_chan: read_buf == NULL?!?\n"); return -EIO; } c = job_control(tty, file); if (c return c; minimum = time = 0; timeout = MAX_SCHEDULE_TIMEOUT; if (!tty->icanon) { time = (HZ / 10) * TIME_CHAR(tty); minimum = MIN_CHAR(tty); if (minimum) { if (time) tty->minimum_to_wake = 1; else if (!waitqueue_active(&tty->read_wait) || (tty->minimum_to_wake > minimum)) tty->minimum_to_wake = minimum; } else { timeout = 0; if (time) { timeout = time; time = 0; } tty->minimum_to_wake = minimum = 1; } }首
先,检查read操作的合法性,read_buf是否已经建立.然后再根据操作的类型来设置tty->
minimum_to_wake.这个成员的含义即为:
如果读进程在因数据不足而睡眠的情况下,数据到达并超过了minimum_to_wake.就将这个读进程唤醒.具体的唤醒过程我们在遇到的时候再进行分
析. /* * Internal serialization of reads. */ //不允许阻塞 if (file->f_flags & O_NONBLOCK) { if (!mutex_trylock(&tty->atomic_read_lock)) return -EAGAIN; } else { if (mutex_lock_interruptible(&tty->atomic_read_lock)) return -ERESTARTSYS; } add_wait_queue(&tty->read_wait, &wait);在不允许睡眠的情况下,调用mutex_trylock()去获得锁.如果锁被占用,马上返回.否则用可中断的方式去获取锁,如果取锁错误,返回失败.如果取锁成功,将进程加至等待队列.在没有数据可读的情况下,直接睡眠.如果有数据可读,将其移出等待队列即可. while (nr) { /* First test for status change. */ if (tty->packet && tty->link->ctrl_status) { unsigned char cs; if (b != buf) break; cs = tty->link->ctrl_status; tty->link->ctrl_status = 0; if (tty_put_user(tty, cs, b++)) { retval = -EFAULT; b--; break; } nr--; break; }接
下来就是一个漫长的while循环,用来读取数据,一直到数据取满为止.如果tty->packet被置为1.即为信包模式,通常用在伪终端设备.
如果tty->link->ctrl_status有数据.则说明如果链路状态发生改变,需要提交此信息.在这种情况下,将其直接copy到
用户空间即可. /* This statement must be first before checking for input so that any interrupt will set the state back to TASK_RUNNING. */ set_current_state(TASK_INTERRUPTIBLE); if (((minimum - (b - buf)) minimum_to_wake) && ((minimum - (b - buf)) >= 1)) tty->minimum_to_wake = (minimum - (b - buf)); if (!input_available_p(tty, 0)) { if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { retval = -EIO; break; } if (tty_hung_up_p(file)) break; if (!timeout) break; if (file->f_flags & O_NONBLOCK) { retval = -EAGAIN; break; } if (signal_pending(current)) { retval = -ERESTARTSYS; break; } n_tty_set_room(tty); timeout = schedule_timeout(timeout); continue; } __set_current_state(TASK_RUNNING);
先将进程设为TASK_INTERRUPTIBLE状态.再调用input_available_p()来判断可数据供读取.如果没有.则进程睡眠.如果
有数据,则将进程状态设为TASK_RUNNING.在终端接收数据的处理过程中,有两种方式,一种是规范模式.一种是原始模式.在规范模式下,终端需要
对数据里面的一些特殊字符做处理.在原始模式下.终端不会对接收到的数据做任何的处理.在这里input_available_p()在判断是否有数据可
读也分两种情况进行,对于规范模式,看是否有已经转换好的数据,对于原始模式,判断接收的信息总数 /* Deal with packet mode. */ //packet模式`忽略 if (tty->packet && b == buf) { if (tty_put_user(tty, TIOCPKT_DATA, b++)) { retval = -EFAULT; b--; break; } nr--; } if (tty->icanon) { /* N.B. avoid overrun if nr == 0 */ while (nr && tty->read_cnt) { int eol; eol = test_and_clear_bit(tty->read_tail, tty->read_flags); c = tty->read_buf[tty->read_tail]; spin_lock_irqsave(&tty->read_lock, flags); tty->read_tail = ((tty->read_tail+1) & (N_TTY_BUF_SIZE-1)); tty->read_cnt--; if (eol) { /* this test should be redundant: * we shouldn't be reading data if * canon_data is 0 */ if (--tty->canon_data tty->canon_data = 0; } spin_unlock_irqrestore(&tty->read_lock, flags); //如果没有到结束字符,将字符copy到数据空间 //__DISABLED_CHAR是不需要copy到用户空间的 if (!eol || (c != __DISABLED_CHAR)) { if (tty_put_user(tty, c, b++)) { retval = -EFAULT; b--; break; } nr--; } if (eol) { //如果遇到行结束符.就可以退出了 tty_audit_push(tty); break; } } if (retval) break; } else { //非加工模式,直接copy int uncopied; //环形缓存,copy两次 uncopied = copy_from_read_buf(tty, &b, &nr); uncopied += copy_from_read_buf(tty, &b, &nr); if (uncopied) { retval = -EFAULT; break; } }对
于规范模式,要读满一行才会返回用户空间.例如我们在shell上输入指令的时候,要按下enter键指令才会进行处理.在
tty->read_flags数组中定义了一些满行的标志,如果read_buf中对应的数据在tty->read_flags中被置位.
就会认为这次读入已经到结尾了.在这里还要注意的是,不要将__DISABLED_CHAR即’/0’拷贝到用户空间.对于原始模式,只需要将read_buf中的数据读入到用户空间就可以返回了.在这里需要注意read_buf是一个环形缓存,需要copy两次.例如tail在head之前的情况. /* If there is enough space in the read buffer now, let the * low-level driver know. We use n_tty_chars_in_buffer() to * check the buffer, as it now knows about canonical mode. * Otherwise, if the driver is throttled and the line is * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode, * we won't get any more characters. */ if (n_tty_chars_in_buffer(tty) n_tty_set_room(tty); check_unthrottle(tty); }OK.到这里,read_buf中或多或少已经有数据被取出了.如果当前的数据量少于TTY_THRESHOLD_UNTHROTTLE.就可以调用check_unthrottle()将其它的写进程唤醒了 if (b - buf >= minimum) break; if (time) timeout = time; } mutex_unlock(&tty->atomic_read_lock); remove_wait_queue(&tty->read_wait, &wait); if (!waitqueue_active(&tty->read_wait)) tty->minimum_to_wake = minimum; __set_current_state(TASK_RUNNING); 已经读完了数据,是该到清理的时候了.将进程移出等待队列,并当进程状态设为TASK_RUNNING size = b - buf; if (size) { retval = size; if (nr) clear_bit(TTY_PUSH, &tty->flags); } else if (test_and_clear_bit(TTY_PUSH, &tty->flags)) goto do_it_again; //更新剩余空间数 n_tty_set_room(tty); return retval;}TTY_PUSH:是由底层驱动程序在读到一个EOF字符并将其放入缓存区造成的,表示用户要尽快将缓存区数据取走.如果本次操作没有读取任何数据,且被设置了TTY_PUSH,则跳转到do_it_again,继续执行.如果本次操作读取了数据,可以等到下一次read的时候再来取.最后,更新read_buf的剩余空间数. 五:控制终端数据的来源从这个函数里面我们可以看到,数据是从read_buf中取出来的,但是谁将数据放入到read_buf中的呢?为了探究出它的根源.我们还得要从vty_init()说起.在之前分析过. vty_init()会调用一个表面字义看起来与键盘相关的一个子函数: kbd_init().跟踪这个函数:int __init kbd_init(void){ int i; int error; for (i = 0; i kbd_table.ledflagstate = KBD_DEFLEDS; kbd_table.default_ledflagstate = KBD_DEFLEDS; kbd_table.ledmode = LED_SHOW_FLAGS; kbd_table.lockstate = KBD_DEFLOCK; kbd_table.slockstate = 0; kbd_table.modeflags = KBD_DEFMODE; kbd_table.kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE; } error = input_register_handler(&kbd_handler); if (error) return error; tasklet_enable(&keyboard_tasklet); tasklet_schedule(&keyboard_tasklet); return 0;}暂时用不到的部份我们先不与分析。 在这里注册了一个input handler。结合前面我们分析的input子系统,在handler里会处理input device上报的事件。跟进这个handler看一下:kbd_handler定义如下:static struct input_handler kbd_handler = { .event = kbd_event, .connect = kbd_connect, .disconnect = kbd_disconnect, .start = kbd_start, .name = "kbd", .id_table = kbd_ids,};Id_table是用来匹配input device的。跟进去看一下,看哪些device的事件,才会交给它处理:static const struct input_device_id kbd_ids[] = { { .flags = INPUT_DEVICE_ID_MATCH_EVBIT, .evbit = { BIT_MASK(EV_KEY) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT, .evbit = { BIT_MASK(EV_SND) }, }, { }, /* Terminating entry */}; 从这个id_table中看来,只要是能支持EV_KEY或者是EV_SND的设备都会被这个hnadler匹配到。相应的。也就能够处理input device上报的事件了.根据之前的input子系统分析,在input device和handler 进行匹配的时候会调用handler->connect.即kbd_connect().代码如下:static int kbd_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id){ struct input_handle *handle; int error; int i; for (i = KEY_RESERVED; i if (test_bit(i, dev->keybit)) break; if (i == BTN_MISC && !test_bit(EV_SND, dev->evbit)) return -ENODEV; handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); if (!handle) return -ENOMEM; handle->dev = dev; handle->handler = handler; handle->name = "kbd"; error = input_register_handle(handle); if (error) goto err_free_handle; error = input_open_device(handle); if (error) goto err_unregister_handle; return 0; err_unregister_handle: input_unregister_handle(handle); err_free_handle: kfree(handle); return error;}在这段代码里,它申请分初始化了一个hande结构,并将其注册。Open。这些都是我们之前分析过的东东。在注册handle的时候。又会调用到hande->start.函数如下:static void kbd_start(struct input_handle *handle){ unsigned char leds = ledstate; tasklet_disable(&keyboard_tasklet); if (leds != 0xff) { input_inject_event(handle, EV_LED, LED_SCROLLL, !!(leds & 0x01)); input_inject_event(handle, EV_LED, LED_NUML, !!(leds & 0x02)); input_inject_event(handle, EV_LED, LED_CAPSL, !!(leds & 0x04)); input_inject_event(handle, EV_SYN, SYN_REPORT, 0); } tasklet_enable(&keyboard_tasklet);}这里就是对键盘上的LED进行操作。启用了tasklent。这些都不是我们所关心的重点。来看下它的事件处理过程:static void kbd_event(struct input_handle *handle, unsigned int event_type, unsigned int event_code, int value){ if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev)) kbd_rawcode(value); if (event_type == EV_KEY) kbd_keycode(event_code, value, HW_RAW(handle->dev)); tasklet_schedule(&keyboard_tasklet); do_poke_blanked_console = 1; schedule_console_callback();}不管对应键盘的那一种模式。后面的数据流程都会转入到input_queue()进等处理。实际上。控制终端由vc_cons[ ]数组表示。数组中的每一个项都表示一个控制终端。由全局变量fg_console来指示当前所用的cosole/另外。对于键盘等输出设备也对应一个数组。即kbd_table[ ].用来表示当前终端的控制信息.其余的都不是我们想关心的。来跟踪一下这个函数的实现:static void put_queue(struct vc_data *vc, int ch){ struct tty_struct *tty = vc->vc_tty; if (tty) { tty_insert_flip_char(tty, ch, 0); con_schedule_flip(tty); }}这里的参数vc就是指的在vc_cons[ ]中的当前项。回忆在console open的时候。初始化了这一项。并建立了VC和tty的关联。就这样。在vc中可以寻着关联关系找到tty了.Tty_insert_filp_char( )将数据ch存入tty的一个缓存中,具体代码如下示:static inline int tty_insert_flip_char(struct tty_struct *tty, unsigned char ch, char flag){ struct tty_buffer *tb = tty->buf.tail; if (tb && tb->used size) { tb->flag_buf_ptr[tb->used] = flag; tb->char_buf_ptr[tb->used++] = ch; return 1; } return tty_insert_flip_string_flags(tty, &ch, &flag, 1);}在这里,将数存先存进了tty->buf中。后面的tty_insert_flip_string_flags是在当前buf不够的情况下,扩张buf使用的。代码比较简单,请自行分析。 将数据暂存之后,会调用con_schedule_flip(tty)去唤醒一个软中断的工作队列.代码如下:static inline void con_schedule_flip(struct tty_struct *t){ unsigned long flags; spin_lock_irqsave(&t->buf.lock, flags); if (t->buf.tail != NULL) t->buf.tail->commit = t->buf.tail->used; spin_unlock_irqrestore(&t->buf.lock, flags); schedule_delayed_work(&t->buf.work, 0);}对应的工作队列为t->buf.work.这个工作队列是怎么定义的呢?这就要回到我们之前分析的tty驱动的tty_struct的初始化.代码片段如下所示:static void initialize_tty_struct(struct tty_struct *tty){ 。。。。。。 。。。。。。。 INIT_DELAYED_WORK(&tty->buf.work, flush_to_ldisc); 。。。。。。}这就是这个工作队列的定义了.在这里,特别提醒一下。在上面的put_queue()处理是处于一个中断环境。回想一想整个事件的流程。是键盘中断àinput device上报事件àhandler处理这个事件àput_queue()在中断中,将工作队列唤醒。将比较繁重的工作交由这个工作队列处理。虽然工作队列也是工作在中断状态。但它是开中断执行的.这也就是软中断存在的目的. 跟进flush_to_ldisc():static void flush_to_ldisc(struct work_struct *work){ struct tty_struct *tty = container_of(work, struct tty_struct, buf.work.work); unsigned long flags; struct tty_ldisc *disc; struct tty_buffer *tbuf, *head; char *char_buf; unsigned char *flag_buf; disc = tty_ldisc_ref(tty); if (disc == NULL) /* !TTY_LDISC */ return;工作队列所调用的参数是它本身所表示的work_queue.而它本身又是封装在tty_strcut里面的。调用container_of()宏就可以获取到封装它的tty_struct.然后增加tty->ldisc的引用计数 spin_lock_irqsave(&tty->buf.lock, flags); /* So we know a flush is running */ set_bit(TTY_FLUSHING, &tty->flags); head = tty->buf.head; if (head != NULL) { tty->buf.head = NULL; for (;;) { int count = head->commit - head->read; if (!count) { if (head->next == NULL) break; tbuf = head; head = head->next; tty_buffer_free(tty, tbuf); continue; } /* Ldisc or user is trying to flush the buffers we are feeding to the ldisc, stop feeding the line discipline as we want to empty the queue */ if (test_bit(TTY_FLUSHPENDING, &tty->flags)) break; if (!tty->receive_room) { schedule_delayed_work(&tty->buf.work, 1); break; } if (count > tty->receive_room) count = tty->receive_room; char_buf = head->char_buf_ptr + head->read; flag_buf = head->flag_buf_ptr + head->read; head->read += count; spin_unlock_irqrestore(&tty->buf.lock, flags); disc->receive_buf(tty, char_buf, flag_buf, count); spin_lock_irqsave(&tty->buf.lock, flags); } /* Restore the queue head */ tty->buf.head = head; }对
于tty->buf中的每个缓存区,如果缓存区中没有数据,则将其释放,这个释放是有优化的。如果数据少于512就将其放到
tty->buf->free中。下次要放分存放空间的时候可以直接到这里面取。如果设置了TTY_
FLUSHPENDING就会跳出循环。如果tty的接收缓存区不够,则跳出循环,定时器到达过后再来调用这个工作队列.最后调用tty->receive_buf()来处理这个数据了. /* We may have a deferred request to flush the input buffer, if so pull the chain under the lock and empty the queue */ if (test_bit(TTY_FLUSHPENDING, &tty->flags)) { __tty_buffer_flush(tty); clear_bit(TTY_FLUSHPENDING, &tty->flags); wake_up(&tty->read_wait); } clear_bit(TTY_FLUSHING, &tty->flags); spin_unlock_irqrestore(&tty->buf.lock, flags); tty_ldisc_deref(disc);}数据最终会通过tty-> receive_buf()将数据放入read_buf.在
这段代码中,有几个很有意思的处理。在进入工作队列的时候,首先会置TTY_FLUSHING标志.如果有进程在读read_buf的时候,如果此标志被
置位,就会设置TTY_FLUSHPENDING标志,并进行睡眠。在数据处理完成之后,判断是否有TTY_FLUSHPENDING标志。如果有,则将
读进程唤醒.并清除TTY_FLUSHPENDING和TTY_FLUSHING想一想。为什么会这么处理呢?为什么这里需要两个缓存区,一个buf.一个read_buf。为什么要这样麻烦呢?首
先,对于缓存区的数目问题:我们在后面会看到。对接收数据还有一系列的预处理过程,这些过程是比较费时的。不宜在中断中进行费时的操作。所以需要选用软中
断机制。这就需要将数据先放置一个buf.再由软中断进行预处理之后,再将它放入到read_buf.这就是两个缓存区的原因.另
外:在存数据到read_buf的时候。会有进程从read_buf中读数据。这样就会造成一个竞争。注意到在软中断情况下是不可睡眠的。我们只能选用自
旋锁一类的机制。而这种机制是禁止中断和抢占的。这又违背了软中断机制的初衷。怎么办呢?这就是这样标志的作用了。在设计中,我们必须首先得要保证软中断
处理机制的快速完成。所以一进入软中断,就置了一个标志。如果有进程来读数据了,也就是说竞争条件发生了,先将读进程置睡眠。不管怎样,先让软中断处理完
之后再说。软中断的工作over这后,再唤醒读进程。我们之前讲的一系统加锁机制是在两者同样平等的情况。而原子置位与判断置位一般是为了保证一方的工作先完成。 好了,到这一步,我们终于看到跟踪read_buf中数据来源问题的一丝曙光了。数据经过tty->receive_buf之后,这个过程就清晰明朗了。对于tty_ldisc_N_TTY. receive_buf接口如下所示:static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count){ const unsigned char *p; char *f, flags = TTY_NORMAL; int i; char buf[64]; unsigned long cpuflags; if (!tty->read_buf) return; if (tty->real_raw) { spin_lock_irqsave(&tty->read_lock, cpuflags); i = min(N_TTY_BUF_SIZE - tty->read_cnt, N_TTY_BUF_SIZE - tty->read_head); i = min(count, i); memcpy(tty->read_buf + tty->read_head, cp, i); tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1); tty->read_cnt += i; cp += i; count -= i; i = min(N_TTY_BUF_SIZE - tty->read_cnt, N_TTY_BUF_SIZE - tty->read_head); i = min(count, i); memcpy(tty->read_buf + tty->read_head, cp, i); tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1); tty->read_cnt += i; spin_unlock_irqrestore(&tty->read_lock, cpuflags); } else { for (i = count, p = cp, f = fp; i; i--, p++) { if (f) flags = *f++; switch (flags) { case TTY_NORMAL: n_tty_receive_char(tty, *p); break; case TTY_BREAK: n_tty_receive_break(tty); break; case TTY_PARITY: case TTY_FRAME: n_tty_receive_parity_error(tty, *p); break; case TTY_OVERRUN: n_tty_receive_overrun(tty); break; default: printk(KERN_ERR "%s: unknown flag %d\n", tty_name(tty, buf), flags); break; } } if (tty->driver->flush_chars) tty->driver->flush_chars(tty); }对于原始模式。直接将数据copy到read_buf中。对于加工模式,将数据预处理之后,再加入到read_buf中。这个预处理过程比较繁杂,这里先忽略. n_tty_set_room(tty); if (!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); if (waitqueue_active(&tty->read_wait)) wake_up_interruptible(&tty->read_wait); } /* * Check the remaining room for the input canonicalization * mode. We don't want to throttle the driver if we're in * canonical mode and don't have a newline yet! */ if (tty->receive_room /* check TTY_THROTTLED first so it indicates our state */ if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) && tty->driver->throttle) tty->driver->throttle(tty); }}重新计数read_buf的剩余空间量。如果可读数据大于tty->minimum_to_wake.就将它的读进程唤醒。如果当前read_buf剩余空间不足TTY_THRESHOLD_THROTTLE.就调用tty->driver->throttle(tty)将数程流入进程先阻塞. 六:控制终端的write操作在输入shell指令的时候,屏幕上会出现我们键入的字符。在输入密码的时候,屏幕上一般不会显示我们当前按入了什么键。就就是终端的两种模式,回显和非回显(ECHO)。当设置为回显模式的时候,会将键入的值在屏幕上面显示出来。这个显示的过程就是通过tty driver->write来实现的。屏幕上的显示操作跟显示驱动有很重要的联系。一般就是调用显卡驱动的显示接口来实现。在切换终端的时候。设置显示区域。由于这部份跟显卡驱动关联较深,而功能又比较单一。在这里不做详细分析。 七:总结在这一节里,将之前分析过的input子系统,tty驱动架构联系在了一起。我们渐渐体会到,Linux中大量的使用分层架构。层与层之前的联系很紧密而维护也很简单。深入体会其中的架构思想。对于我们平时做开发是很有裨益的.
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/108121/showart_2122646.html |
|