TTY終端的輸入過程 ================= 1) 當用戶按壓鍵盤時, 鍵盤中斷處理程序將經過轉換的鍵盤功能碼用tty_insert_flip_char()放入到當前打開終端的翻轉緩衝區之中, 然後將緩衝區輸出任務函數(flush_to_ldisc)添加到控制檯任務隊列(con_task_queue)並激活控制檯軟中斷執行該任務函數. flush_to_ldisc()翻轉讀寫緩衝區, 將緩衝區接收數據傳遞給tty終端規程的n_tty_receive_buf()接收函數, n_tty_receive_buf()處理輸入字符, 將輸出字符緩衝在終端的循環緩衝區(read_buf)之中. 用戶通過tty規程的read_chan()讀取read_buf中的字符. 當多個進程同時讀取同一終端時, 使用tty->atomic_read信號燈來競爭讀取權. 2) 每一打開的終端接口中包含終端的參數結構(termios), 當終端打開時, 它將繼承終端驅動設備參數表中參數結構, 系統缺省的終端參數爲tty_std_termios, 驅動設備的初始終端參數爲init_termios. 終端參數可由用戶通過ioctl()調整, 用來控制終端接口的行爲. c_iflag描述對輸入字符的識別模式, c_oflag描述對輸出字符的處理模式, c_cflag描述終端接口的控制模式, c_lflag描述字符處理過程中各種本地行爲, 還有由19個字符組成的c_cc字符參數表, 描述各類控制字符以及輔助參數. 3) 當本地模式具有規範輸入標誌(ICANON)時, 輸出數據按行拷貝給用戶. 在接收到換行符(或者文件結束符c_cc[VEOF],行結束符c_cc[VEOL]和c_cc[VEOL2])之前, 可以使用編輯控制符完成簡單的編輯. c_cc[VERASE]刪除前一字符, c_cc[VKILL]刪除整行, c_cc[VWERASE]刪除前一單詞. 4) 當本地模式具有回顯標誌(ECHO)時, 輸入字符按一定的規則回顯到輸出設備上. 當本地模式具有信號標誌(ISIG)時, 特定的輸入字符可以在終端當前進程組中產生信號. c_cc[VINTR]產生中斷信號, c_cc[VQUIT]產生退出信息, c_cc[VSUSP]產生暫停信號. c_cc[VTIME]定義了用戶讀取數據的等待超時, 單位爲0.1秒, c_cc[VMIN]爲所期望的字節數. 在非ICANON模式下, 如果在c_cc[VTIME]時間內接收到c_cc[VMIN]數量的字符, 讀過程返回. c_cc[VSTOP]暫停驅動設備輸出, c_cc[VSTART]啓動驅動設備輸出. 5) 內核初始化結束後, 打開控制檯終端設備/dev/console作爲輸入輸出去運行/sbin/init. /dev/console的設備號是0x0501, 在打開時被定向到命令行(console=)指定的終端設備, 缺省情況下爲/dev/tty1, 其設備號爲0x0401. init程序完成用戶初始化完成後啓動若干個終端守護程序(getty). getty在打開所守護的終端文件之前先使用setsid()創建會話組併成爲會話組的首領進程(leader=1), 首領進程的會話號(session)和進程組號(pgrp)等於進程號(pid). 首領進程打開終端文件後成爲終端的控制進程, 其tty指針指向該終端打開結構, 該終端也成爲本次會話的控制終端, 其session號和pgrp號分別等於進程的session號和pgrp號. 當用戶從終端登錄後, 登錄bash程序置換getty進程成爲首領進程. 進程的會話號和組號隨着進程的複製而傳遞, 子進程的首領標誌復位. bash在用execve()執行用戶程序之前將用setpgid()爲每一程序建立屬於它自已的進程組(使進程組號等於進程號), 再用對終端的(TIOCSPGRP)設備控制將終端進程組號設爲將該進程號, 使終端切換到新的進程組. 終端進程組以外的進程組如果進行讀操作將在其進程組中生成SIGTTIN信號, 缺省的信號處理器使進程進入暫停. ; drivers/char/keyboard.c: void put_queue(int ch) { wake_up(&keypress_wait); if (tty) { tty_insert_flip_char(tty, ch, 0); con_schedule_flip(tty); } } static void puts_queue(char *cp) { wake_up(&keypress_wait); if (!tty) return; while (*cp) { tty_insert_flip_char(tty, *cp, 0); cp++; } con_schedule_flip(tty); } _INLINE_ void tty_insert_flip_char(struct tty_struct *tty, unsigned char ch, char flag) { if (tty->flip.count < TTY_FLIPBUF_SIZE) { tty->flip.count++; *tty->flip.flag_buf_ptr++ = flag; *tty->flip.char_buf_ptr++ = ch; } } extern inline void con_schedule_flip(struct tty_struct *t) { queue_task(&t->flip.tqueue, &con_task_queue); tasklet_schedule(&console_tasklet); } ; drivers/char/tty_io.c: /* * When a break, frame error, or parity error happens, these codes are * stuffed into the flags buffer. */ #define TTY_NORMAL 0 #define TTY_BREAK 1 #define TTY_FRAME 2 #define TTY_PARITY 3 #define TTY_OVERRUN 4 #define INTR_CHAR(tty) ((tty)->termios->c_cc[VINTR]) #define QUIT_CHAR(tty) ((tty)->termios->c_cc[VQUIT]) #define ERASE_CHAR(tty) ((tty)->termios->c_cc[VERASE]) #define KILL_CHAR(tty) ((tty)->termios->c_cc[VKILL]) #define EOF_CHAR(tty) ((tty)->termios->c_cc[VEOF]) #define TIME_CHAR(tty) ((tty)->termios->c_cc[VTIME]) #define MIN_CHAR(tty) ((tty)->termios->c_cc[VMIN]) #define SWTC_CHAR(tty) ((tty)->termios->c_cc[VSWTC]) #define START_CHAR(tty) ((tty)->termios->c_cc[VSTART]) #define STOP_CHAR(tty) ((tty)->termios->c_cc[VSTOP]) #define SUSP_CHAR(tty) ((tty)->termios->c_cc[VSUSP]) #define EOL_CHAR(tty) ((tty)->termios->c_cc[VEOL]) #define REPRINT_CHAR(tty) ((tty)->termios->c_cc[VREPRINT]) #define DISCARD_CHAR(tty) ((tty)->termios->c_cc[VDISCARD]) #define WERASE_CHAR(tty) ((tty)->termios->c_cc[VWERASE]) #define LNEXT_CHAR(tty) ((tty)->termios->c_cc[VLNEXT]) #define EOL2_CHAR(tty) ((tty)->termios->c_cc[VEOL2]) #define _I_FLAG(tty,f) ((tty)->termios->c_iflag & (f)) #define _O_FLAG(tty,f) ((tty)->termios->c_oflag & (f)) #define _C_FLAG(tty,f) ((tty)->termios->c_cflag & (f)) #define _L_FLAG(tty,f) ((tty)->termios->c_lflag & (f)) #define I_IGNBRK(tty) _I_FLAG((tty),IGNBRK) #define I_BRKINT(tty) _I_FLAG((tty),BRKINT) #define I_IGNPAR(tty) _I_FLAG((tty),IGNPAR) #define I_PARMRK(tty) _I_FLAG((tty),PARMRK) #define I_INPCK(tty) _I_FLAG((tty),INPCK) #define I_ISTRIP(tty) _I_FLAG((tty),ISTRIP) #define I_INLCR(tty) _I_FLAG((tty),INLCR) #define I_IGNCR(tty) _I_FLAG((tty),IGNCR) #define I_ICRNL(tty) _I_FLAG((tty),ICRNL) #define I_IUCLC(tty) _I_FLAG((tty),IUCLC) #define I_IXON(tty) _I_FLAG((tty),IXON) #define I_IXANY(tty) _I_FLAG((tty),IXANY) #define I_IXOFF(tty) _I_FLAG((tty),IXOFF) #define I_IMAXBEL(tty) _I_FLAG((tty),IMAXBEL) #define O_OPOST(tty) _O_FLAG((tty),OPOST) #define O_OLCUC(tty) _O_FLAG((tty),OLCUC) #define O_ONLCR(tty) _O_FLAG((tty),ONLCR) #define O_OCRNL(tty) _O_FLAG((tty),OCRNL) #define O_ONOCR(tty) _O_FLAG((tty),ONOCR) #define O_ONLRET(tty) _O_FLAG((tty),ONLRET) #define O_OFILL(tty) _O_FLAG((tty),OFILL) #define O_OFDEL(tty) _O_FLAG((tty),OFDEL) #define O_NLDLY(tty) _O_FLAG((tty),NLDLY) #define O_CRDLY(tty) _O_FLAG((tty),CRDLY) #define O_TABDLY(tty) _O_FLAG((tty),TABDLY) #define O_BSDLY(tty) _O_FLAG((tty),BSDLY) #define O_VTDLY(tty) _O_FLAG((tty),VTDLY) #define O_FFDLY(tty) _O_FLAG((tty),FFDLY) #define C_BAUD(tty) _C_FLAG((tty),CBAUD) #define C_CSIZE(tty) _C_FLAG((tty),CSIZE) #define C_CSTOPB(tty) _C_FLAG((tty),CSTOPB) #define C_CREAD(tty) _C_FLAG((tty),CREAD) #define C_PARENB(tty) _C_FLAG((tty),PARENB) #define C_PARODD(tty) _C_FLAG((tty),PARODD) #define C_HUPCL(tty) _C_FLAG((tty),HUPCL) #define C_CLOCAL(tty) _C_FLAG((tty),CLOCAL) #define C_CIBAUD(tty) _C_FLAG((tty),CIBAUD) #define C_CRTSCTS(tty) _C_FLAG((tty),CRTSCTS) #define L_ISIG(tty) _L_FLAG((tty),ISIG) #define L_ICANON(tty) _L_FLAG((tty),ICANON) #define L_XCASE(tty) _L_FLAG((tty),XCASE) #define L_ECHO(tty) _L_FLAG((tty),ECHO) #define L_ECHOE(tty) _L_FLAG((tty),ECHOE) #define L_ECHOK(tty) _L_FLAG((tty),ECHOK) #define L_ECHONL(tty) _L_FLAG((tty),ECHONL) #define L_NOFLSH(tty) _L_FLAG((tty),NOFLSH) #define L_TOSTOP(tty) _L_FLAG((tty),TOSTOP) #define L_ECHOCTL(tty) _L_FLAG((tty),ECHOCTL) #define L_ECHOPRT(tty) _L_FLAG((tty),ECHOPRT) #define L_ECHOKE(tty) _L_FLAG((tty),ECHOKE) #define L_FLUSHO(tty) _L_FLAG((tty),FLUSHO) #define L_PENDIN(tty) _L_FLAG((tty),PENDIN) #define L_IEXTEN(tty) _L_FLAG((tty),IEXTEN) /* number of characters left in xmit buffer before select has we have room */ #define WAKEUP_CHARS 256 typedef unsigned char cc_t; typedef unsigned int speed_t; typedef unsigned int tcflag_t; #define NCCS 19 struct termios { tcflag_t c_iflag; /* input mode flags */ tcflag_t c_oflag; /* output mode flags */ tcflag_t c_cflag; /* control mode flags */ tcflag_t c_lflag; /* local mode flags */ cc_t c_line; /* line discipline */ cc_t c_cc[NCCS]; /* control characters */ }; /* intr=^C quit=^\ erase=del kill=^U eof=^D vtime=\0 vmin=\1 sxtc=\0 start=^Q stop=^S susp=^Z eol=\0 reprint=^R discard=^U werase=^W lnext=^V eol2=\0 */ #define INIT_C_CC "\003\034\177\025\004\0\1\0\021\023\032\0\022\017\027\026\0" /* c_cc characters */ #define VINTR 0 #define VQUIT 1 #define VERASE 2 #define VKILL 3 #define VEOF 4 #define VTIME 5 #define VMIN 6 #define VSWTC 7 #define VSTART 8 #define VSTOP 9 #define VSUSP 10 #define VEOL 11 #define VREPRINT 12 #define VDISCARD 13 #define VWERASE 14 #define VLNEXT 15 #define VEOL2 16 /* c_iflag bits */ #define IGNBRK 0000001 #define BRKINT 0000002 #define IGNPAR 0000004 #define PARMRK 0000010 #define INPCK 0000020 #define ISTRIP 0000040 #define INLCR 0000100 #define IGNCR 0000200 #define ICRNL 0000400 #define IUCLC 0001000 #define IXON 0002000 #define IXANY 0004000 #define IXOFF 0010000 #define IMAXBEL 0020000 /* c_oflag bits */ #define OPOST 0000001 #define OLCUC 0000002 #define ONLCR 0000004 #define OCRNL 0000010 #define ONOCR 0000020 #define ONLRET 0000040 #define OFILL 0000100 #define OFDEL 0000200 #define NLDLY 0000400 #define NL0 0000000 #define NL1 0000400 #define CRDLY 0003000 #define CR0 0000000 #define CR1 0001000 #define CR2 0002000 #define CR3 0003000 #define TABDLY 0014000 #define TAB0 0000000 #define TAB1 0004000 #define TAB2 0010000 #define TAB3 0014000 #define XTABS 0014000 #define BSDLY 0020000 #define BS0 0000000 #define BS1 0020000 #define VTDLY 0040000 #define VT0 0000000 #define VT1 0040000 #define FFDLY 0100000 #define FF0 0000000 #define FF1 0100000 /* c_cflag bit meaning */ #define CBAUD 0010017 #define B0 0000000 /* hang up */ #define B50 0000001 #define B75 0000002 #define B110 0000003 #define B134 0000004 #define B150 0000005 #define B200 0000006 #define B300 0000007 #define B600 0000010 #define B1200 0000011 #define B1800 0000012 #define B2400 0000013 #define B4800 0000014 #define B9600 0000015 #define B19200 0000016 #define B38400 0000017 #define EXTA B19200 #define EXTB B38400 #define CSIZE 0000060 #define CS5 0000000 #define CS6 0000020 #define CS7 0000040 #define CS8 0000060 #define CSTOPB 0000100 #define CREAD 0000200 #define PARENB 0000400 #define PARODD 0001000 #define HUPCL 0002000 #define CLOCAL 0004000 #define CBAUDEX 0010000 #define B57600 0010001 #define B115200 0010002 #define B230400 0010003 #define B460800 0010004 #define B500000 0010005 #define B576000 0010006 #define B921600 0010007 #define B1000000 0010010 #define B1152000 0010011 #define B1500000 0010012 #define B2000000 0010013 #define B2500000 0010014 #define B3000000 0010015 #define B3500000 0010016 #define B4000000 0010017 #define CIBAUD 002003600000 /* input baud rate (not used) */ #define CMSPAR 010000000000 /* mark or space (stick) parity */ #define CRTSCTS 020000000000 /* flow control */ /* c_lflag bits */ #define ISIG 0000001 #define ICANON 0000002 #define XCASE 0000004 #define ECHO 0000010 #define ECHOE 0000020 #define ECHOK 0000040 #define ECHONL 0000100 #define NOFLSH 0000200 #define TOSTOP 0000400 #define ECHOCTL 0001000 #define ECHOPRT 0002000 #define ECHOKE 0004000 #define FLUSHO 0010000 #define PENDIN 0040000 #define IEXTEN 0100000 /* * This defines the low- and high-watermarks for throttling and * unthrottling the TTY driver. These watermarks are used for * controlling the space in the read buffer. */ #define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */ #define TTY_THRESHOLD_UNTHROTTLE 128 static struct file_operations tty_fops = { llseek: tty_lseek, read: tty_read, write: tty_write, poll: tty_poll, ioctl: tty_ioctl, open: tty_open, release: tty_release, fasync: tty_fasync, }; void __init console_init(void) { /* Setup the default TTY line discipline. */ memset(ldiscs, 0, sizeof(ldiscs)); (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY); /* * Set up the standard termios. Individual tty drivers may * deviate from this; this is used as a template. */ memset(&tty_std_termios, 0, sizeof(struct termios)); memcpy(tty_std_termios.c_cc, INIT_C_CC, NCCS); tty_std_termios.c_iflag = ICRNL | IXON; tty_std_termios.c_oflag = OPOST | ONLCR; tty_std_termios.c_cflag = B38400 | CS8 | CREAD | HUPCL; tty_std_termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; ... } static ssize_t tty_read(struct file * file, char * buf, size_t count, loff_t *ppos) { int i; struct tty_struct * tty; struct inode *inode; /* Can't seek (pread) on ttys. */ if (ppos != &file->f_pos) return -ESPIPE; tty = (struct tty_struct *)file->private_data; inode = file->f_dentry->d_inode; if (tty_paranoia_check(tty, inode->i_rdev, "tty_read")) 校驗tty結構是否完整 return -EIO; if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags))) return -EIO; /* This check not only needs to be done before reading, but also whenever read_chan() gets woken up after sleeping, so I've moved it to there. This should only be done for the N_TTY line discipline, anyway. Same goes for write_chan(). -- jlc. */ #if 0 if ((inode->i_rdev != CONSOLE_DEV) && /* don't stop on /dev/console */ (tty->pgrp > 0) && (current->tty == tty) && (tty->pgrp != current->pgrp)) if (is_ignored(SIGTTIN) || is_orphaned_pgrp(current->pgrp)) return -EIO; else { (void) kill_pg(current->pgrp, SIGTTIN, 1); return -ERESTARTSYS; } #endif lock_kernel(); if (tty->ldisc.read) i = (tty->ldisc.read)(tty,file,buf,count); else i = -EIO; unlock_kernel(); if (i > 0) inode->i_atime = CURRENT_TIME; return i; } /* * This routine is called out of the software interrupt to flush data * from the flip buffer to the line discipline. */ static void flush_to_ldisc(void *private_) { struct tty_struct *tty = (struct tty_struct *) private_; unsigned char *cp; char *fp; int count; unsigned long flags; if (test_bit(TTY_DONT_FLIP, &tty->flags)) { 如果不允許翻轉讀寫緩衝區 queue_task(&tty->flip.tqueue, &tq_timer); 將任務延遲一個時鐘中斷 return; } if (tty->flip.buf_num) { cp = tty->flip.char_buf + TTY_FLIPBUF_SIZE; fp = tty->flip.flag_buf + TTY_FLIPBUF_SIZE; tty->flip.buf_num = 0; save_flags(flags); cli(); tty->flip.char_buf_ptr = tty->flip.char_buf; tty->flip.flag_buf_ptr = tty->flip.flag_buf; } else { cp = tty->flip.char_buf; fp = tty->flip.flag_buf; tty->flip.buf_num = 1; save_flags(flags); cli(); tty->flip.char_buf_ptr = tty->flip.char_buf + TTY_FLIPBUF_SIZE; tty->flip.flag_buf_ptr = tty->flip.flag_buf + TTY_FLIPBUF_SIZE; } count = tty->flip.count; tty->flip.count = 0; restore_flags(flags); tty->ldisc.receive_buf(tty, cp, fp, count); } void start_tty(struct tty_struct *tty) { if (!tty->stopped || tty->flow_stopped) return; tty->stopped = 0; if (tty->link && tty->link->packet) { tty->ctrl_status &= ~TIOCPKT_STOP; tty->ctrl_status |= TIOCPKT_START; wake_up_interruptible(&tty->link->read_wait); } if (tty->driver.start) (tty->driver.start)(tty); if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup)(tty); wake_up_interruptible(&tty->write_wait); } void stop_tty(struct tty_struct *tty) { if (tty->stopped) return; tty->stopped = 1; if (tty->link && tty->link->packet) { tty->ctrl_status &= ~TIOCPKT_START; tty->ctrl_status |= TIOCPKT_STOP; wake_up_interruptible(&tty->link->read_wait); } if (tty->driver.stop) (tty->driver.stop)(tty); } ; drivers/char/n_tty.c: struct tty_ldisc tty_ldisc_N_TTY = { TTY_LDISC_MAGIC, /* magic */ "n_tty", /* name */ 0, /* num */ 0, /* flags */ n_tty_open, /* open */ n_tty_close, /* close */ n_tty_flush_buffer, /* flush_buffer */ n_tty_chars_in_buffer, /* chars_in_buffer */ read_chan, /* read */ write_chan, /* write */ n_tty_ioctl, /* ioctl */ n_tty_set_termios, /* set_termios */ normal_poll, /* poll */ n_tty_receive_buf, /* receive_buf */ n_tty_receive_room, /* receive_room */ 0 /* write_wakeup */ }; static ssize_t read_chan(struct tty_struct *tty, struct file *file, unsigned char *buf, size_t nr) { unsigned char *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("n_tty_read_chan: called with read_buf == NULL?!?\n"); return -EIO; } /* Job control check -- must be done at start and after every sleep (POSIX.1 7.1.1.4). */ /* NOTE: not yet done after every sleep pending a thorough check of the logic of this change. -- jlc */ /* don't stop on /dev/console */ if (file->f_dentry->d_inode->i_rdev != CONSOLE_DEV && 打開的不是/dev/tty設備 file->f_dentry->d_inode->i_rdev != SYSCONS_DEV && 打開的不是/dev/console設備 current->tty == tty) { if (tty->pgrp <= 0) printk("read_chan: tty->pgrp <= 0!\n"); else if (current->pgrp != tty->pgrp) { 如果當前進程不屬於終端的當前進程組 if (is_ignored(SIGTTIN) || 如果SIGTTIN信號被忽略 is_orphaned_pgrp(current->pgrp)) 如果當前進程組的父進程已退出 return -EIO; kill_pg(current->pgrp, SIGTTIN, 1); return -ERESTARTSYS; } } minimum = time = 0; timeout = MAX_SCHEDULE_TIMEOUT; if (!tty->icanon) { 如果不是規範輸入模式 time = (HZ / 10) * TIME_CHAR(tty); 計算超時時間, 時間字節的單位爲0.1秒 minimum = MIN_CHAR(tty); 返回的最少字符數 if (minimum) { if (time) tty->minimum_to_wake = 1; 接收到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; } } if (file->f_flags & O_NONBLOCK) { if (down_trylock(&tty->atomic_read)) return -EAGAIN; } else { if (down_interruptible(&tty->atomic_read)) return -ERESTARTSYS; } add_wait_queue(&tty->read_wait, &wait); set_bit(TTY_DONT_FLIP, &tty->flags); 禁止緩衝區翻轉 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; put_user(cs, b++); nr--; break; } /* 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; } clear_bit(TTY_DONT_FLIP, &tty->flags); timeout = schedule_timeout(timeout); set_bit(TTY_DONT_FLIP, &tty->flags); continue; } current->state = TASK_RUNNING; /* Deal with packet mode. */ if (tty->packet && b == buf) { put_user(TIOCPKT_DATA, b++); 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); if (!eol || (c != __DISABLED_CHAR)) { put_user(c, b++); nr--; } if (eol) break; } } else { int uncopied; uncopied = copy_from_read_buf(tty, &b, &nr); uncopied += copy_from_read_buf(tty, &b, &nr); if (uncopied) { retval = -EFAULT; break; } } /* 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) check_unthrottle(tty); 如果讀緩衝區的字符小於128, 打開節流的輸入設備 if (b - buf >= minimum) break; if (time) timeout = time; } clear_bit(TTY_DONT_FLIP, &tty->flags); up(&tty->atomic_read); remove_wait_queue(&tty->read_wait, &wait); if (!waitqueue_active(&tty->read_wait)) tty->minimum_to_wake = minimum; current->state = 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; return retval; } int is_ignored(int sig) { return (sigismember(¤t->blocked, sig) || current->sig->action[sig-1].sa.sa_handler == SIG_IGN); } static inline int input_available_p(struct tty_struct *tty, int amt) { if (tty->icanon) { if (tty->canon_data) return 1; } else if (tty->read_cnt >= (amt ? amt : 1)) return 1; return 0; } int tty_hung_up_p(struct file * filp) { return (filp->f_op == &hung_up_tty_fops); } /* * Helper function to speed up read_chan. It is only called when * ICANON is off; it copies characters straight from the tty queue to * user space directly. It can be profitably called twice; once to * drain the space from the tail pointer to the (physical) end of the * buffer, and once to drain the space from the (physical) beginning of * the buffer to head pointer. */ static inline int copy_from_read_buf(struct tty_struct *tty, unsigned char **b, size_t *nr) { int retval; ssize_t n; unsigned long flags; retval = 0; spin_lock_irqsave(&tty->read_lock, flags); n = MIN(*nr, MIN(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail)); spin_unlock_irqrestore(&tty->read_lock, flags); if (n) { mb(); retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n); n -= retval; spin_lock_irqsave(&tty->read_lock, flags); tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1); tty->read_cnt -= n; spin_unlock_irqrestore(&tty->read_lock, flags); *b += n; *nr -= n; } return retval; } /* * Return number of characters buffered to be delivered to user */ ssize_t n_tty_chars_in_buffer(struct tty_struct *tty) { unsigned long flags; ssize_t n = 0; spin_lock_irqsave(&tty->read_lock, flags); if (!tty->icanon) { n = tty->read_cnt; } else if (tty->canon_data) { n = (tty->canon_head > tty->read_tail) ? tty->canon_head - tty->read_tail : tty->canon_head + (N_TTY_BUF_SIZE - tty->read_tail); } spin_unlock_irqrestore(&tty->read_lock, flags); return n; } /* * Check whether to call the driver.unthrottle function. * We test the TTY_THROTTLED bit first so that it always * indicates the current state. */ static void check_unthrottle(struct tty_struct * tty) { if (tty->count && test_and_clear_bit(TTY_THROTTLED, &tty->flags) && tty->driver.unthrottle) tty->driver.unthrottle(tty); } 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(count, MIN(N_TTY_BUF_SIZE - tty->read_cnt, N_TTY_BUF_SIZE - tty->read_head)); 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(count, MIN(N_TTY_BUF_SIZE - tty->read_cnt, N_TTY_BUF_SIZE - tty->read_head)); 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("%s: unknown flag %d\n", tty_name(tty, buf), flags); break; } } if (tty->driver.flush_chars) tty->driver.flush_chars(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 (n_tty_receive_room(tty) < TTY_THRESHOLD_THROTTLE) { 如果剩餘空間小於128字節 /* 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); 進行節流處理 } } static inline void n_tty_receive_break(struct tty_struct *tty) { if (I_IGNBRK(tty)) 如果忽略中斷字符 return; if (I_BRKINT(tty)) { 如果中斷字符產生中信號 isig(SIGINT, tty, 1); 給終端所在的進程組發送INT信號 return; } if (I_PARMRK(tty)) { 斷點標記 put_tty_queue('\377', tty); put_tty_queue('\0', tty); } put_tty_queue('\0', tty); wake_up_interruptible(&tty->read_wait); } static inline void n_tty_receive_overrun(struct tty_struct *tty) { char buf[64]; tty->num_overrun++; if (time_before(tty->overrun_time, jiffies - HZ)) { printk("%s: %d input overrun(s)\n", tty_name(tty, buf), tty->num_overrun); tty->overrun_time = jiffies; tty->num_overrun = 0; } } static inline void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c) { if (I_IGNPAR(tty)) { return; } if (I_PARMRK(tty)) { put_tty_queue('\377', tty); put_tty_queue('\0', tty); put_tty_queue(c, tty); } else if (I_INPCK(tty)) 允許奇偶校驗 put_tty_queue('\0', tty); else put_tty_queue(c, tty); wake_up_interruptible(&tty->read_wait); } static int n_tty_receive_room(struct tty_struct *tty) { int left = N_TTY_BUF_SIZE - tty->read_cnt - 1; /* * If we are doing input canonicalization, and there are no * pending newlines, let characters through without limit, so * that erase characters will be handled. Other excess * characters will be beeped. */ if (tty->icanon && !tty->canon_data) return N_TTY_BUF_SIZE; if (left > 0) return left; return 0; } static inline void put_tty_queue_nolock(unsigned char c, struct tty_struct *tty) { if (tty->read_cnt < N_TTY_BUF_SIZE) { tty->read_buf[tty->read_head] = c; tty->read_head = (tty->read_head + 1) & (N_TTY_BUF_SIZE-1); tty->read_cnt++; } } static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c) { unsigned long flags; if (tty->raw) { put_tty_queue(c, tty); return; } if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) { 如果軟件流控制 start_tty(tty); return; } if (I_ISTRIP(tty)) 如果去除字符最高位 c &= 0x7f; if (I_IUCLC(tty) && L_IEXTEN(tty)) 如果允許小寫轉換擴展模式 c=tolower(c); if (tty->closing) { 如果正在關閉終端 if (I_IXON(tty)) { if (c == START_CHAR(tty)) start_tty(tty); else if (c == STOP_CHAR(tty)) stop_tty(tty); } return; } /* * If the previous character was LNEXT, or we know that this * character is not one of the characters that we'll have to * handle specially, do shortcut processing to speed things * up. */ if (!test_bit(c, &tty->process_char_map) || tty->lnext) { finish_erasing(tty); tty->lnext = 0; if (L_ECHO(tty)) { 如果輸入字符回顯 if (tty->read_cnt >= N_TTY_BUF_SIZE-1) { 如果輸入緩衝區滿 put_char('\a', tty); /* beep if no space */ 向顯示終端發送響鈴字符 return; } /* Record the column of first canon char. */ if (tty->canon_head == tty->read_head) tty->canon_column = tty->column; echo_char(c, tty); } if (I_PARMRK(tty) && c == (unsigned char) '\377') put_tty_queue(c, tty); put_tty_queue(c, tty); return; } if (c == '\r') { if (I_IGNCR(tty)) 忽略回車符 return; if (I_ICRNL(tty)) 回車轉換爲換行符 c = '\n'; } else if (c == '\n' && I_INLCR(tty)) 換行符轉換成回車符 c = '\r'; if (I_IXON(tty)) { 如果爲軟件流控 if (c == START_CHAR(tty)) { 如果爲啓動字符 start_tty(tty); 打開終端 return; } if (c == STOP_CHAR(tty)) { 如果爲停止字符 stop_tty(tty); 暫停終端 return; } } if (L_ISIG(tty)) { 如果允許觸發信號 int signal; signal = SIGINT; if (c == INTR_CHAR(tty)) 如果爲中斷字符 goto send_signal; signal = SIGQUIT; if (c == QUIT_CHAR(tty)) 如果爲退出字符 goto send_signal; signal = SIGTSTP; if (c == SUSP_CHAR(tty)) { 如果爲暫停字符 send_signal: isig(signal, tty, 0); 向終端所在的進程組發送相應的信號 return; } } if (tty->icanon) { if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) || (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) { eraser(c, tty); return; } if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) { tty->lnext = 1; if (L_ECHO(tty)) { finish_erasing(tty); if (L_ECHOCTL(tty)) { put_char('^', tty); put_char('\b', tty); } } return; } if (c == REPRINT_CHAR(tty) && L_ECHO(tty) && L_IEXTEN(tty)) { unsigned long tail = tty->canon_head; finish_erasing(tty); echo_char(c, tty); opost('\n', tty); while (tail != tty->read_head) { echo_char(tty->read_buf[tail], tty); tail = (tail+1) & (N_TTY_BUF_SIZE-1); } return; } if (c == '\n') { if (L_ECHO(tty) || L_ECHONL(tty)) { if (tty->read_cnt >= N_TTY_BUF_SIZE-1) { put_char('\a', tty); return; } opost('\n', tty); } goto handle_newline; } if (c == EOF_CHAR(tty)) { if (tty->canon_head != tty->read_head) set_bit(TTY_PUSH, &tty->flags); c = __DISABLED_CHAR; goto handle_newline; } if ((c == EOL_CHAR(tty)) || (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) { /* * XXX are EOL_CHAR and EOL2_CHAR echoed?!? */ if (L_ECHO(tty)) { if (tty->read_cnt >= N_TTY_BUF_SIZE-1) { put_char('\a', tty); return; } /* Record the column of first canon char. */ if (tty->canon_head == tty->read_head) tty->canon_column = tty->column; echo_char(c, tty); } /* * XXX does PARMRK doubling happen for * EOL_CHAR and EOL2_CHAR? */ if (I_PARMRK(tty) && c == (unsigned char) '\377') put_tty_queue(c, tty); handle_newline: spin_lock_irqsave(&tty->read_lock, flags); set_bit(tty->read_head, &tty->read_flags); put_tty_queue_nolock(c, tty); tty->canon_head = tty->read_head; tty->canon_data++; spin_unlock_irqrestore(&tty->read_lock, flags); kill_fasync(&tty->fasync, SIGIO, POLL_IN); if (waitqueue_active(&tty->read_wait)) wake_up_interruptible(&tty->read_wait); return; } } finish_erasing(tty); if (L_ECHO(tty)) { if (tty->read_cnt >= N_TTY_BUF_SIZE-1) { put_char('\a', tty); /* beep if no space */ return; } if (c == '\n') opost('\n', tty); else { /* Record the column of first canon char. */ if (tty->canon_head == tty->read_head) tty->canon_column = tty->column; echo_char(c, tty); } } if (I_PARMRK(tty) && c == (unsigned char) '\377') put_tty_queue(c, tty); put_tty_queue(c, tty); } static inline void put_char(unsigned char c, struct tty_struct *tty) { tty->driver.put_char(tty, c); } /* Must be called only when L_ECHO(tty) is true. */ static void echo_char(unsigned char c, struct tty_struct *tty) { if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t') { put_char('^', tty); 回顯控制字符 put_char(c ^ 0100, tty); tty->column += 2; } else opost(c, tty); 回顯字符 } static inline void finish_erasing(struct tty_struct *tty) { if (tty->erasing) { put_char('/', tty); tty->column += 2; tty->erasing = 0; } } static inline void isig(int sig, struct tty_struct *tty, int flush) { if (tty->pgrp > 0) kill_pg(tty->pgrp, sig, 1); if (flush || !L_NOFLSH(tty)) { n_tty_flush_buffer(tty); if (tty->driver.flush_buffer) tty->driver.flush_buffer(tty); } } static void eraser(unsigned char c, struct tty_struct *tty) { enum { ERASE, WERASE, KILL } kill_type; int head, seen_alnums; unsigned long flags; if (tty->read_head == tty->canon_head) { 如果輸入爲空 /* opost('\a', tty); */ /* what do you think? */ return; } if (c == ERASE_CHAR(tty)) kill_type = ERASE; else if (c == WERASE_CHAR(tty)) kill_type = WERASE; else { if (!L_ECHO(tty)) { 如果禁止回顯 spin_lock_irqsave(&tty->read_lock, flags); tty->read_cnt -= ((tty->read_head - tty->canon_head) & (N_TTY_BUF_SIZE - 1)); tty->read_head = tty->canon_head; spin_unlock_irqrestore(&tty->read_lock, flags); return; } if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) { spin_lock_irqsave(&tty->read_lock, flags); tty->read_cnt -= ((tty->read_head - tty->canon_head) & (N_TTY_BUF_SIZE - 1)); tty->read_head = tty->canon_head; spin_unlock_irqrestore(&tty->read_lock, flags); finish_erasing(tty); echo_char(KILL_CHAR(tty), tty); /* Add a newline if ECHOK is on and ECHOKE is off. */ if (L_ECHOK(tty)) opost('\n', tty); return; } kill_type = KILL; } seen_alnums = 0; while (tty->read_head != tty->canon_head) { head = (tty->read_head - 1) & (N_TTY_BUF_SIZE-1); c = tty->read_buf[head]; if (kill_type == WERASE) { /* Equivalent to BSD's ALTWERASE. */ if (isalnum(c) || c == '_') seen_alnums++; else if (seen_alnums) break; } spin_lock_irqsave(&tty->read_lock, flags); tty->read_head = head; tty->read_cnt--; spin_unlock_irqrestore(&tty->read_lock, flags); if (L_ECHO(tty)) { if (L_ECHOPRT(tty)) { if (!tty->erasing) { put_char('\\', tty); tty->column++; tty->erasing = 1; } echo_char(c, tty); } else if (kill_type == ERASE && !L_ECHOE(tty)) { echo_char(ERASE_CHAR(tty), tty); } else if (c == '\t') { unsigned int col = tty->canon_column; unsigned long tail = tty->canon_head; /* Find the column of the last char. */ while (tail != tty->read_head) { c = tty->read_buf[tail]; if (c == '\t') col = (col | 7) + 1; else if (iscntrl(c)) { if (L_ECHOCTL(tty)) col += 2; } else col++; tail = (tail+1) & (N_TTY_BUF_SIZE-1); } /* should never happen */ if (tty->column > 0x80000000) tty->column = 0; /* Now backup to that column. */ while (tty->column > col) { /* Can't use opost here. */ put_char('\b', tty); if (tty->column > 0) tty->column--; } } else { if (iscntrl(c) && L_ECHOCTL(tty)) { put_char('\b', tty); put_char(' ', tty); put_char('\b', tty); if (tty->column > 0) tty->column--; } if (!iscntrl(c) || L_ECHOCTL(tty)) { put_char('\b', tty); put_char(' ', tty); put_char('\b', tty); if (tty->column > 0) tty->column--; } } } if (kill_type == ERASE) break; } if (tty->read_head == tty->canon_head) finish_erasing(tty); } /* * Perform OPOST processing. Returns -1 when the output device is * full and the character must be retried. */ static int opost(unsigned char c, struct tty_struct *tty) 輸出預處理 { int space, spaces; space = tty->driver.write_room(tty); 取輸出設備剩餘緩衝區空間 if (!space) return -1; if (O_OPOST(tty)) { 如果允許輸出預處理 switch (c) { case '\n': if (O_ONLRET(tty)) 換行時是否回車 tty->column = 0; if (O_ONLCR(tty)) { if (space < 2) return -1; tty->driver.put_char(tty, '\r'); tty->column = 0; } tty->canon_column = tty->column; break; case '\r': if (O_ONOCR(tty) && tty->column == 0) 禁止重複輸出回車符 return 0; if (O_OCRNL(tty)) { 回車符轉換爲換行符 c = '\n'; if (O_ONLRET(tty)) tty->canon_column = tty->column = 0; break; } tty->canon_column = tty->column = 0; break; case '\t': spaces = 8 - (tty->column & 7); if (O_TABDLY(tty) == XTABS) { if (space < spaces) return -1; tty->column += spaces; tty->driver.write(tty, 0, " ", spaces); return 0; } tty->column += spaces; break; case '\b': if (tty->column > 0) tty->column--; 退格 break; default: if (O_OLCUC(tty)) 小寫轉換成大寫 c = toupper(c); if (!iscntrl(c)) 如果不是控制符 tty->column++; break; } } tty->driver.put_char(tty, c); return 0; } /* * Reset the read buffer counters, clear the flags, * and make sure the driver is unthrottled. Called * from n_tty_open() and n_tty_flush_buffer(). */ 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); check_unthrottle(tty); } static int n_tty_open(struct tty_struct *tty) { if (!tty) return -EINVAL; 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_buffer_flags(tty); tty->column = 0; n_tty_set_termios(tty, 0); tty->minimum_to_wake = 1; tty->closing = 0; return 0; } /* * This character is the same as _POSIX_VDISABLE: it cannot be used as * a c_cc[] character, but indicates that a particular special character * isn't in use (eg VINTR has no character etc) */ #define __DISABLED_CHAR '\0' static void n_tty_set_termios(struct tty_struct *tty, struct termios * old) { if (!tty) return; tty->icanon = (L_ICANON(tty) != 0); if (test_bit(TTY_HW_COOK_IN, &tty->flags)) { tty->raw = 1; tty->real_raw = 1; return; } if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) || I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) || I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) || I_PARMRK(tty)) { cli(); memset(tty->process_char_map, 0, 256/8); if (I_IGNCR(tty) || I_ICRNL(tty)) set_bit('\r', &tty->process_char_map); if (I_INLCR(tty)) set_bit('\n', &tty->process_char_map); if (L_ICANON(tty)) { set_bit(ERASE_CHAR(tty), &tty->process_char_map); set_bit(KILL_CHAR(tty), &tty->process_char_map); set_bit(EOF_CHAR(tty), &tty->process_char_map); set_bit('\n', &tty->process_char_map); set_bit(EOL_CHAR(tty), &tty->process_char_map); if (L_IEXTEN(tty)) { set_bit(WERASE_CHAR(tty), &tty->process_char_map); set_bit(LNEXT_CHAR(tty), &tty->process_char_map); set_bit(EOL2_CHAR(tty), &tty->process_char_map); if (L_ECHO(tty)) set_bit(REPRINT_CHAR(tty), &tty->process_char_map); } } if (I_IXON(tty)) { set_bit(START_CHAR(tty), &tty->process_char_map); set_bit(STOP_CHAR(tty), &tty->process_char_map); } if (L_ISIG(tty)) { set_bit(INTR_CHAR(tty), &tty->process_char_map); set_bit(QUIT_CHAR(tty), &tty->process_char_map); set_bit(SUSP_CHAR(tty), &tty->process_char_map); } clear_bit(__DISABLED_CHAR, &tty->process_char_map); sti(); tty->raw = 0; tty->real_raw = 0; } else { tty->raw = 1; if ((I_IGNBRK(tty) || (!I_BRKINT(tty) && !I_PARMRK(tty))) && (I_IGNPAR(tty) || !I_INPCK(tty)) && (tty->driver.flags & TTY_DRIVER_REAL_RAW)) tty->real_raw = 1; else tty->real_raw = 0; } }
TTY終端的輸入過程
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.