📄 tty_io.c
字号:
/*
* linux/kernel/tty_io.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
/*
* 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles
* or rs-channels. It also implements echoing, cooked mode etc.
*
* Kill-line thanks to John T Kohl, who also corrected VMIN = VTIME = 0.
*
* Modified by Theodore Ts'o, 9/14/92, to dynamically allocate the
* tty_struct and tty_queue structures. Previously there was a array
* of 256 tty_struct's which was statically allocated, and the
* tty_queue structures were allocated at boot time. Both are now
* dynamically allocated only when the tty is open.
*
* Also restructured routines so that there is more of a separation
* between the high-level tty routines (tty_io.c and tty_ioctl.c) and
* the low-level tty routines (serial.c, pty.c, console.c). This
* makes for cleaner and more compact code. -TYT, 9/17/92
*
* Modified by Fred N. van Kempen, 01/29/93, to add line disciplines
* which can be dynamically activated and de-activated by the line
* discipline handling modules (like SLIP).
*
* NOTE: pay no attention to the line discpline code (yet); its
* interface is still subject to change in this version...
* -- TYT, 1/31/92
*
* Added functionality to the OPOST tty handling. No delays, but all
* other bits should be there.
* -- Nick Holloway <alfie@dcs.warwick.ac.uk>, 27th May 1993.
*
* Rewrote canonical mode and added more termios flags.
* -- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94
*/
#include <linux/types.h>
#include <linux/major.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/fcntl.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/timer.h>
#include <linux/ctype.h>
#include <linux/kd.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/malloc.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include "kbd_kern.h"
#include "vt_kern.h"
#define CONSOLE_DEV MKDEV(TTY_MAJOR,0)
#define MAX_TTYS 256
struct tty_struct *tty_table[MAX_TTYS];
struct termios *tty_termios[MAX_TTYS]; /* We need to keep the termios state */
/* around, even when a tty is closed */
struct termios *termios_locked[MAX_TTYS]; /* Bitfield of locked termios flags*/
struct tty_ldisc ldiscs[NR_LDISCS]; /* line disc dispatch table */
int tty_check_write[MAX_TTYS/32]; /* bitfield for the bh handler */
/*
* fg_console is the current virtual console,
* redirect is the pseudo-tty that console output
* is redirected to if asked by TIOCCONS.
*/
int fg_console = 0;
struct tty_struct * redirect = NULL;
struct wait_queue * keypress_wait = NULL;
static void initialize_tty_struct(int line, struct tty_struct *tty);
static void initialize_termios(int line, struct termios *tp);
static int tty_read(struct inode *, struct file *, char *, int);
static int tty_write(struct inode *, struct file *, char *, int);
static int tty_select(struct inode *, struct file *, int, select_table *);
static int tty_open(struct inode *, struct file *);
static void tty_release(struct inode *, struct file *);
int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
{
if (disc < N_TTY || disc >= NR_LDISCS)
return -EINVAL;
if (new_ldisc) {
ldiscs[disc] = *new_ldisc;
ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
} else
memset(&ldiscs[disc], 0, sizeof(struct tty_ldisc));
return 0;
}
void put_tty_queue(unsigned char c, struct tty_queue * queue)
{
int head;
unsigned long flags;
save_flags(flags);
cli();
head = (queue->head + 1) & (TTY_BUF_SIZE-1);
if (head != queue->tail) {
queue->buf[queue->head] = c;
queue->head = head;
}
restore_flags(flags);
}
int get_tty_queue(struct tty_queue * queue)
{
int result = -1;
unsigned long flags;
save_flags(flags);
cli();
if (queue->tail != queue->head) {
result = queue->buf[queue->tail];
INC(queue->tail);
}
restore_flags(flags);
return result;
}
/*
* This routine copies out a maximum of buflen characters from the
* read_q; it is a convenience for line disciplines so they can grab a
* large block of data without calling get_tty_char directly. It
* returns the number of characters actually read. Return terminates
* if an error character is read from the queue and the return value
* is negated.
*/
int tty_read_raw_data(struct tty_struct *tty, unsigned char *bufp, int buflen)
{
int result = 0;
unsigned char *p = bufp;
unsigned long flags;
int head, tail;
int ok = 1;
save_flags(flags);
cli();
tail = tty->read_q.tail;
head = tty->read_q.head;
while ((result < buflen) && (tail!=head) && ok) {
ok = !clear_bit (tail, &tty->readq_flags);
*p++ = tty->read_q.buf[tail++];
tail &= TTY_BUF_SIZE-1;
result++;
}
tty->read_q.tail = tail;
restore_flags(flags);
return (ok) ? result : -result;
}
void tty_write_flush(struct tty_struct * tty)
{
if (!tty->write || EMPTY(&tty->write_q))
return;
if (set_bit(TTY_WRITE_BUSY,&tty->flags))
return;
tty->write(tty);
if (!clear_bit(TTY_WRITE_BUSY,&tty->flags))
printk("tty_write_flush: bit already cleared\n");
}
void tty_read_flush(struct tty_struct * tty)
{
if (!tty || EMPTY(&tty->read_q))
return;
if (set_bit(TTY_READ_BUSY, &tty->flags))
return;
ldiscs[tty->disc].handler(tty);
if (!clear_bit(TTY_READ_BUSY, &tty->flags))
printk("tty_read_flush: bit already cleared\n");
}
static int hung_up_tty_read(struct inode * inode, struct file * file, char * buf, int count)
{
return 0;
}
static int hung_up_tty_write(struct inode * inode, struct file * file, char * buf, int count)
{
return -EIO;
}
static int hung_up_tty_select(struct inode * inode, struct file * filp, int sel_type, select_table * wait)
{
return 1;
}
static int hung_up_tty_ioctl(struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg)
{
return -EIO;
}
static int tty_lseek(struct inode * inode, struct file * file, off_t offset, int orig)
{
return -ESPIPE;
}
static struct file_operations tty_fops = {
tty_lseek,
tty_read,
tty_write,
NULL, /* tty_readdir */
tty_select,
tty_ioctl,
NULL, /* tty_mmap */
tty_open,
tty_release
};
static struct file_operations hung_up_tty_fops = {
tty_lseek,
hung_up_tty_read,
hung_up_tty_write,
NULL, /* hung_up_tty_readdir */
hung_up_tty_select,
hung_up_tty_ioctl,
NULL, /* hung_up_tty_mmap */
NULL, /* hung_up_tty_open */
tty_release /* hung_up_tty_release */
};
void do_tty_hangup(struct tty_struct * tty, struct file_operations *fops)
{
int i;
struct file * filp;
struct task_struct *p;
int dev;
if (!tty)
return;
dev = MKDEV(TTY_MAJOR,tty->line);
for (filp = first_file, i=0; i<nr_files; i++, filp = filp->f_next) {
if (!filp->f_count)
continue;
if (filp->f_rdev != dev)
continue;
if (filp->f_inode && filp->f_inode->i_rdev == CONSOLE_DEV)
continue;
if (filp->f_op != &tty_fops)
continue;
filp->f_op = fops;
}
flush_input(tty);
flush_output(tty);
wake_up_interruptible(&tty->secondary.proc_list);
if (tty->session > 0) {
kill_sl(tty->session,SIGHUP,1);
kill_sl(tty->session,SIGCONT,1);
}
tty->session = 0;
tty->pgrp = -1;
for_each_task(p) {
if (p->tty == tty->line)
p->tty = -1;
}
if (tty->hangup)
(tty->hangup)(tty);
}
void tty_hangup(struct tty_struct * tty)
{
#ifdef TTY_DEBUG_HANGUP
printk("tty%d hangup...\n", tty->line);
#endif
do_tty_hangup(tty, &hung_up_tty_fops);
}
void tty_vhangup(struct tty_struct * tty)
{
#ifdef TTY_DEBUG_HANGUP
printk("tty%d vhangup...\n", tty->line);
#endif
do_tty_hangup(tty, &hung_up_tty_fops);
}
int tty_hung_up_p(struct file * filp)
{
return (filp->f_op == &hung_up_tty_fops);
}
/*
* This function is typically called only by the session leader, when
* it wants to dissassociate itself from its controlling tty.
*
* It performs the following functions:
* (1) Sends a SIGHUP and SIGCONT to the foreground process group
* (2) Clears the tty from being controlling the session
* (3) Clears the controlling tty for all processes in the
* session group.
*/
void disassociate_ctty(int priv)
{
struct tty_struct *tty;
struct task_struct *p;
if (current->tty >= 0) {
tty = tty_table[current->tty];
if (tty) {
if (tty->pgrp > 0) {
kill_pg(tty->pgrp, SIGHUP, priv);
kill_pg(tty->pgrp, SIGCONT, priv);
}
tty->session = 0;
tty->pgrp = -1;
} else
printk("disassociate_ctty: ctty is NULL?!?");
}
for_each_task(p)
if (p->session == current->session)
p->tty = -1;
}
/*
* Sometimes we want to wait until a particular VT has been activated. We
* do it in a very simple manner. Everybody waits on a single queue and
* get woken up at once. Those that are satisfied go on with their business,
* while those not ready go back to sleep. Seems overkill to add a wait
* to each vt just for this - usually this does nothing!
*/
static struct wait_queue *vt_activate_queue = NULL;
/*
* Sleeps until a vt is activated, or the task is interrupted. Returns
* 0 if activation, -1 if interrupted.
*/
int vt_waitactive(void)
{
interruptible_sleep_on(&vt_activate_queue);
return (current->signal & ~current->blocked) ? -1 : 0;
}
#define vt_wake_waitactive() wake_up(&vt_activate_queue)
extern int kill_proc(int pid, int sig, int priv);
/*
* Performs the back end of a vt switch
*/
void complete_change_console(unsigned int new_console)
{
unsigned char old_vc_mode;
if (new_console == fg_console || new_console >= NR_CONSOLES)
return;
/*
* If we're switching, we could be going from KD_GRAPHICS to
* KD_TEXT mode or vice versa, which means we need to blank or
* unblank the screen later.
*/
old_vc_mode = vt_cons[fg_console].vc_mode;
update_screen(new_console);
/*
* If this new console is under process control, send it a signal
* telling it that it has acquired. Also check if it has died and
* clean up (similar to logic employed in change_console())
*/
if (vt_cons[new_console].vt_mode.mode == VT_PROCESS)
{
/*
* Send the signal as privileged - kill_proc() will
* tell us if the process has gone or something else
* is awry
*/
if (kill_proc(vt_cons[new_console].vt_pid,
vt_cons[new_console].vt_mode.acqsig,
1) != 0)
{
/*
* The controlling process has died, so we revert back to
* normal operation. In this case, we'll also change back
* to KD_TEXT mode. I'm not sure if this is strictly correct
* but it saves the agony when the X server dies and the screen
* remains blanked due to KD_GRAPHICS! It would be nice to do
* this outside of VT_PROCESS but there is no single process
* to account for and tracking tty count may be undesirable.
*/
vt_cons[new_console].vc_mode = KD_TEXT;
clr_vc_kbd_mode(kbd_table + new_console, VC_RAW);
clr_vc_kbd_mode(kbd_table + new_console, VC_MEDIUMRAW);
vt_cons[new_console].vt_mode.mode = VT_AUTO;
vt_cons[new_console].vt_mode.waitv = 0;
vt_cons[new_console].vt_mode.relsig = 0;
vt_cons[new_console].vt_mode.acqsig = 0;
vt_cons[new_console].vt_mode.frsig = 0;
vt_cons[new_console].vt_pid = -1;
vt_cons[new_console].vt_newvt = -1;
}
}
/*
* We do this here because the controlling process above may have
* gone, and so there is now a new vc_mode
*/
if (old_vc_mode != vt_cons[new_console].vc_mode)
{
if (vt_cons[new_console].vc_mode == KD_TEXT)
unblank_screen();
else {
timer_active &= ~(1<<BLANK_TIMER);
blank_screen();
}
}
/*
* Wake anyone waiting for their VT to activate
*/
vt_wake_waitactive();
return;
}
/*
* Performs the front-end of a vt switch
*/
void change_console(unsigned int new_console)
{
if (new_console == fg_console || new_console >= NR_CONSOLES)
return;
/*
* If this vt is in process mode, then we need to handshake with
* that process before switching. Essentially, we store where that
* vt wants to switch to and wait for it to tell us when it's done
* (via VT_RELDISP ioctl).
*
* We also check to see if the controlling process still exists.
* If it doesn't, we reset this vt to auto mode and continue.
* This is a cheap way to track process control. The worst thing
* that can happen is: we send a signal to a process, it dies, and
* the switch gets "lost" waiting for a response; hopefully, the
* user will try again, we'll detect the process is gone (unless
* the user waits just the right amount of time :-) and revert the
* vt to auto control.
*/
if (vt_cons[fg_console].vt_mode.mode == VT_PROCESS)
{
/*
* Send the signal as privileged - kill_proc() will
* tell us if the process has gone or something else
* is awry
*/
if (kill_proc(vt_cons[fg_console].vt_pid,
vt_cons[fg_console].vt_mode.relsig,
1) == 0)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -