对于规范模式,要读满一行才会返回用户空间.例如我们在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) <= TTY_THRESHOLD_UNTHROTTLE) {
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 < MAX_NR_CONSOLES; i++) {
kbd_table[i].ledflagstate = KBD_DEFLEDS;
kbd_table[i].default_ledflagstate = KBD_DEFLEDS;
kbd_table[i].ledmode = LED_SHOW_FLAGS;
kbd_table[i].lockstate = KBD_DEFLOCK;
kbd_table[i].slockstate = 0;
kbd_table[i].modeflags = KBD_DEFMODE;
kbd_table[i].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 < BTN_MISC; 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)