一:前言
我們在之前分析過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") < 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 < __con_initcall_end) {
(*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 < 0)
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)) < tty->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 < 0)
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;
}
}
?
評論
查看更多