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; } }