TTY终端设备文件接口的基本结构

来源:互联网 发布:linux查看数据库状态 编辑:程序博客网 时间:2024/05/18 00:43
 TTY终端设备文件接口的基本结构
=============================
1) 在操作系统中, 应用程序一般不能直接操作硬件, 只能通过系统调用按照内核所提供的抽象形式来间接访问硬件. 在Linux系统中, 所有的硬件对象抽象为文件对象, 通过对文件的系统调用来访问. 用户对文件的系统调用操作基于该文件对象的操作函数表(file_operations), 每一打开的文件都继承了某类文件操作表. 对于一般文件来说(包括目录文件和符号链接文件), 文件对象继承了它所在文件系统的某种文件操作表. 对于设备文件来说, 文件对象继承了它的主设备号所在驱动程序的文件操作表. 设备的文件操作表是设备驱动的起点, 但往往并不在这个层次上直接完成设备操作. 如果在某个层次上对不同对象的某类操作具有某种共性, 那么就可以象提取"公因式"那样将这类操作抽象出来, 然后建立更低一级的操作层次来区分不同的对象, 这样就形成了不同层次的驱动接口.
2) 打开终端的文件操作表为tty_fops, 终端的打开结构用tty_struct结构描述, 文件结构的private_data指针指向打开的终端结构, 如果当前进程的终端指针(current->tty)指向某个打开的终端结构, 则表示该进程与此终端相关联. tty_driver结构描述终端的输出设备, 终端线路规程(tty_ldisc)用来在文件接口与输入设备和输出设备之间进行调控. TTY规程(N_TTY)用于连接终端输入驱动设备和终端显示驱动设备, PPP规程(N_PPP)用来连接终端驱动设备和网络驱动设备.
3) 当用户写入终端文件时, 通过TTY规程的write_chan操作间接输出到终端输出设备. 终端的输入设备将接收数据写入终端打开结构的轮换缓冲区(tty_flip_buffer)之中, 轮换缓冲区使用两个各512字节的缓冲区交替进行读写, 使数据的接收和处理互不重叠同时进行. 轮换缓冲区包含字符数据和字符标志两组缓冲区, 每一字符都具有对应的属性字符. 接收的数据在任务队列中传递给线路规程的receive_buf进行操作. 对于TTY规程, receive_buf操作对接收字符按照终端协议进行处理, 输出数据最终转移到打开终端的read_buf缓冲区中供用户接收. 对于PPP规程, receive_buf操作将识别接收到的PPP帧, 转变为IP帧作为ppp设备的接收帧处理.
4) 伪终端设备是一种特殊的终端驱动设备, 它并不驱动某个物理设备, 而是用来将终端的输出定向到应用程序中进行处理. 伪终端设备由主-从两个成对的设备构成, 当打开主设备(pty)时, 对应的从设备(ttyp)随之打开, 形成连接状态. 输入到主设备数据成为从设备的输出, 输入到从设备的数据成为主设备的输出, 形成双向管道. 伪终端设备常用于远程登录服务器来建立网络和终端的关联. 当通过telnet远程登录到另一台主机时, telnet进程与远程主机的telnet服务器相连接. telnet服务器使用某个pty主设备并通过对应的ttyp从设备与你在远程主机上所运行的程序相互通信.
5) Linux内核是一个以文件为对象的系统, 用户进程的虚拟空间是可执行文件的映射框架, CPU在可执行文件的映射空间中运行. 用户的文件接口实际上是字符流的接口, UNIX将人机接口归结为与终端文件的接口. 我想与文件相对的另一种对象就是窗口, 它们在不同的方向上产生了截然不同的人机界面风格.
6) 熟悉Windows API编程的都知道, Windows应用程序初始化时需要用RegisterClass()注册自已所定义的窗口类, 然后就可以用该窗口类创建它的窗口实例. 从驱动程序观点来看, 每一个窗口类可看成一个以窗口为对象的驱动程序, 所谓窗口元件就是一种窗口驱动设备, Windows应用程序则是在整个操作系统范围内((而不是内核范围内)以窗口为对象的各种层次的驱动程序集合. 如果将窗口类的消息函数(WndProc)看成第一个窗口驱动层次, 那么MFC所提供的应用程序接口可看成更末端层次的一种窗口驱动接口(用户只要在MFC基础上定义一些枝节的操作就可以了). 按照这种推理, 窗口系统由窗口管理核心和各种窗口驱动程序构成, 窗口应用程序则以窗口系统某种抽象而又统一的接口与窗口对象交互.
; drivers/char/tty_io.c:
/*
* Where all of the state associated with a tty is kept while the tty
* is open.  Since the termios state should be kept even if the tty
* has been closed --- for things like the baud rate, etc --- it is
* not stored here, but rather a pointer to the real state is stored
* here.  Possible the winsize structure should have the same
* treatment, but (1) the default 80x24 is usually right and (2) it's
* most often used by a windowing system, which will set the correct
* size each time the window is created or resized anyway.
* IMPORTANT: since this structure is dynamically allocated, it must
* be no larger than 4096 bytes.  Changing TTY_FLIPBUF_SIZE will change
* the size of this structure, and it needs to be done with care.
* - TYT, 9/14/92
*/
struct tty_struct { 终端设备文件的打开结构
int magic;
struct tty_driver driver;
struct tty_ldisc ldisc;
struct termios *termios, *termios_locked;
int pgrp;
int session;
kdev_t device;
unsigned long flags;
int count;
struct winsize winsize;
unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1;
unsigned char low_latency:1, warned:1;
unsigned char ctrl_status;
struct tty_struct *link;
struct fasync_struct *fasync;
struct tty_flip_buffer flip;
int max_flip_cnt;
int alt_speed; /* For magic substitution of 38400 bps */
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct tq_struct tq_hangup;
void *disc_data;
void *driver_data;
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
/*
* The following is data for the N_TTY line discipline.  For
* historical reasons, this is included in the tty structure.
*/
unsigned int column;
unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
unsigned char closing:1;
unsigned short minimum_to_wake;
unsigned overrun_time;
int num_overrun;
unsigned long process_char_map[256/(8*sizeof(unsigned long))];
char *read_buf;
int read_head;
int read_tail;
int read_cnt;
unsigned long read_flags[N_TTY_BUF_SIZE/(8*sizeof(unsigned long))];
int canon_data;
unsigned long canon_head;
unsigned int canon_column;
struct semaphore atomic_read;
struct semaphore atomic_write;
spinlock_t read_lock;
};
struct fasync_struct {
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
};
#define CONSOLE_DEV MKDEV(TTY_MAJOR,0)
#define TTY_DEV MKDEV(TTYAUX_MAJOR,0)
#define SYSCONS_DEV MKDEV(TTYAUX_MAJOR,1)
#define PTMX_DEV MKDEV(TTYAUX_MAJOR,2)
/*
* This is the flip buffer used for the tty driver.  The buffer is
* located in the tty structure, and is used as a high speed interface
* between the tty driver and the tty line discipline.
*/
#define TTY_FLIPBUF_SIZE 512
struct tty_flip_buffer {
struct tq_struct tqueue;
struct semaphore pty_sem;
char *char_buf_ptr;
unsigned char *flag_buf_ptr;
int count;
int buf_num;
unsigned char char_buf[2*TTY_FLIPBUF_SIZE];
char flag_buf[2*TTY_FLIPBUF_SIZE];
unsigned char slop[4]; /* N.B. bug overwrites buffer by 1 */
};
struct tty_driver { 终端驱动设备结构
int magic; /* magic number for this structure */
const char *driver_name;
const char *name;
int name_base; /* offset of printed name */
short major; /* major device number */
short minor_start; /* start of minor device number*/
short num; /* number of devices */
short type; /* type of tty driver */
short subtype; /* subtype of tty driver */
struct termios init_termios; /* Initial termios */
int flags; /* tty driver flags */
int *refcount; /* for loadable tty drivers */
struct proc_dir_entry *proc_entry; /* /proc fs entry */
struct tty_driver *other; /* only used for the PTY driver */
/*
* Pointer to the tty data structures
*/
struct tty_struct **table;
struct termios **termios;
struct termios **termios_locked;
void *driver_state; /* only used for the PTY driver */
/*
* Interface routines from the upper tty layer to the tty
* driver.
*/
int  (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
int  (*write)(struct tty_struct * tty, int from_user,
      const unsigned char *buf, int count);
void (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int  (*write_room)(struct tty_struct *tty);
int  (*chars_in_buffer)(struct tty_struct *tty);
int  (*ioctl)(struct tty_struct *tty, struct file * file,
    unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct termios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
void (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*read_proc)(char *page, char **start, off_t off,
  int count, int *eof, void *data);
int (*write_proc)(struct file *file, const char *buffer,
  unsigned long count, void *data);
/*
* linked list pointers
*/
struct tty_driver *next;
struct tty_driver *prev;
};
struct tty_ldisc { 终端线路规程
int magic;
char *name;
int num;
int flags;
/*
* The following routines are called from above.
*/
int (*open)(struct tty_struct *);
void (*close)(struct tty_struct *);
void (*flush_buffer)(struct tty_struct *tty);
ssize_t (*chars_in_buffer)(struct tty_struct *tty);
ssize_t (*read)(struct tty_struct * tty, struct file * file,
unsigned char * buf, size_t nr);
ssize_t (*write)(struct tty_struct * tty, struct file * file,
const unsigned char * buf, size_t nr);
int (*ioctl)(struct tty_struct * tty, struct file * file,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct termios * old);
unsigned int (*poll)(struct tty_struct *, struct file *,
     struct poll_table_struct *);
/*
* The following routines are called from below.
*/
void (*receive_buf)(struct tty_struct *, const unsigned char *cp,
       char *fp, int count);
int (*receive_room)(struct tty_struct *);
void (*write_wakeup)(struct tty_struct *);
};
static inline int devfs_register_chrdev (unsigned int major, const char *name,
struct file_operations *fops)
{
    return register_chrdev (major, name, fops);
}
struct termios tty_std_termios; /* for the benefit of tty drivers  */
struct tty_driver *tty_drivers; /* linked list of tty drivers */
struct tty_ldisc ldiscs[NR_LDISCS]; /* line disc dispatch table */
/*
* redirect is the pseudo-tty that console output
* is redirected to if asked by TIOCCONS.
*/
struct tty_struct * redirect;
static struct tty_driver dev_tty_driver, dev_syscons_driver;
#ifdef CONFIG_UNIX98_PTYS
static struct tty_driver dev_ptmx_driver;
#endif
#ifdef CONFIG_VT
static struct tty_driver dev_console_driver;
#endif
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,
};
/*
* Ok, now we can initialize the rest of the tty devices and can count
* on memory allocations, interrupts etc..
*/
void __init tty_init(void)
{
if (sizeof(struct tty_struct) > PAGE_SIZE)
panic("size of tty structure > PAGE_SIZE!");
/*
* dev_tty_driver and dev_console_driver are actually magic
* devices which get redirected at open time.  Nevertheless,
* we register them so that register_chrdev is called
* appropriately.
*/
memset(&dev_tty_driver, 0, sizeof(struct tty_driver));
dev_tty_driver.magic = TTY_DRIVER_MAGIC;
dev_tty_driver.driver_name = "/dev/tty";
dev_tty_driver.name = dev_tty_driver.driver_name + 5;
dev_tty_driver.name_base = 0;
dev_tty_driver.major = TTYAUX_MAJOR;
dev_tty_driver.minor_start = 0;
dev_tty_driver.num = 1;
dev_tty_driver.type = TTY_DRIVER_TYPE_SYSTEM;
dev_tty_driver.subtype = SYSTEM_TYPE_TTY;
if (tty_register_driver(&dev_tty_driver)) 注册当前终端的重定向设备
panic("Couldn't register /dev/tty driver\n");
dev_syscons_driver = dev_tty_driver;
dev_syscons_driver.driver_name = "/dev/console";
dev_syscons_driver.name = dev_syscons_driver.driver_name + 5;
dev_syscons_driver.major = TTYAUX_MAJOR;
dev_syscons_driver.minor_start = 1;
dev_syscons_driver.type = TTY_DRIVER_TYPE_SYSTEM;
dev_syscons_driver.subtype = SYSTEM_TYPE_SYSCONS;
if (tty_register_driver(&dev_syscons_driver)) 注册内核控制台的重定向设备
panic("Couldn't register /dev/console driver\n");
/* console calls tty_register_driver() before kmalloc() works.
* Thus, we can't devfs_register() then.  Do so now, instead.
*/
#ifdef CONFIG_VT
con_init_devfs();
#endif
#ifdef CONFIG_UNIX98_PTYS
dev_ptmx_driver = dev_tty_driver;
dev_ptmx_driver.driver_name = "/dev/ptmx";
dev_ptmx_driver.name = dev_ptmx_driver.driver_name + 5;
dev_ptmx_driver.major= MAJOR(PTMX_DEV);
dev_ptmx_driver.minor_start = MINOR(PTMX_DEV);
dev_ptmx_driver.type = TTY_DRIVER_TYPE_SYSTEM;
dev_ptmx_driver.subtype = SYSTEM_TYPE_SYSPTMX;
if (tty_register_driver(&dev_ptmx_driver))
panic("Couldn't register /dev/ptmx driver\n");
#endif
#ifdef CONFIG_VT
dev_console_driver = dev_tty_driver;
dev_console_driver.driver_name = "/dev/vc/0";
dev_console_driver.name = dev_console_driver.driver_name + 5;
dev_console_driver.major = TTY_MAJOR;
dev_console_driver.type = TTY_DRIVER_TYPE_SYSTEM;
dev_console_driver.subtype = SYSTEM_TYPE_CONSOLE;
if (tty_register_driver(&dev_console_driver)) 注册当前控制台的重定向设备
panic("Couldn't register /dev/tty0 driver\n");
kbd_init();
#endif
pty_init();
#ifdef CONFIG_VT
vcs_init();
#endif
}
/*
* Called by a tty driver to register itself.
*/
int tty_register_driver(struct tty_driver *driver) 注册终端驱动设备
{
int error;
        int i;
if (driver->flags & TTY_DRIVER_INSTALLED)
return 0;
error = devfs_register_chrdev(driver->major, driver->name, &tty_fops); 注册终端驱动设备的文件接口
if (error major == 0) 从最大可用的主设备号开始自动分配主设备号
driver->major = error;
if (!driver->put_char)
driver->put_char = tty_default_put_char;
driver->prev = 0;
driver->next = tty_drivers;
if (tty_drivers) tty_drivers->prev = driver;
tty_drivers = driver;
if ( !(driver->flags & TTY_DRIVER_NO_DEVFS) ) {
for(i = 0; i num; i++)
    tty_register_devfs(driver, 0, driver->minor_start + i);
}
proc_tty_register_driver(driver);
return error;
}
/*
* tty_open and tty_release keep up the tty count that contains the
* number of opens done on a tty. We cannot use the inode-count, as
* different inodes might point to the same tty.
*
* Open-counting is needed for pty masters, as well as for keeping
* track of serial lines: DTR is dropped when the last close happens.
* (This is not done solely through tty->count, now.  - Ted 1/27/92)
*
* The termios state of a pty is reset on first open so that
* settings don't persist across reuse.
*/
static int tty_open(struct inode * inode, struct file * filp)
{
struct tty_struct *tty;
int noctty, retval;
kdev_t device;
unsigned short saved_flags;
char buf[64];
saved_flags = filp->f_flags;
retry_open:
noctty = filp->f_flags & O_NOCTTY; 是否终端绑定到打开进程
device = inode->i_rdev; 取终端文件的设备号
if (device == TTY_DEV) { 如果打开当前进程的终端设备
if (!current->tty) 如果没有终端绑定到当前进程
return -ENXIO;
device = current->tty->device; 取当前进程的终端设备号
filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
/* noctty = 1; */
}
#ifdef CONFIG_VT
if (device == CONSOLE_DEV) { 如果打开当前控制台设备
extern int fg_console;
device = MKDEV(TTY_MAJOR, fg_console + 1); 取前景虚拟控制台设备号
noctty = 1; 不将该终端绑定到打开进程
}
#endif
if (device == SYSCONS_DEV) { 如果打开的是系统控制台设备
struct console *c = console_drivers;
while(c && !c->device)
c = c->next; 取第1个内核控制台设备
if (!c)
                        return -ENODEV;
                device = c->device(c); 取实际的终端设备号
filp->f_flags |= O_NONBLOCK; /* Don't let /dev/console block */
noctty = 1;
}
if (device == PTMX_DEV) {
#ifdef CONFIG_UNIX98_PTYS
/* find a free pty. */
int major, minor;
struct tty_driver *driver;
/* find a device that is not in use. */
retval = -1;
for ( major = 0 ; major minor_start ;
     minor minor_start + driver->num ;
     minor++) {
device = MKDEV(driver->major, minor);
if (!init_dev(device, &tty)) goto ptmx_found; /* ok! */
}
}
return -EIO; /* no free ptys */
ptmx_found:
set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */
minor -= driver->minor_start;
devpts_pty_new(driver->other->name_base + minor, MKDEV(driver->other->major, minor + driver->other->minor_start));
tty_register_devfs(&pts_driver[major], DEVFS_FL_NO_PERSISTENCE,
   pts_driver[major].minor_start + minor);
noctty = 1;
goto init_dev_done;
#else   /* CONFIG_UNIX_98_PTYS */
return -ENODEV;
#endif  /* CONFIG_UNIX_98_PTYS */
}
retval = init_dev(device, &tty); 建立终端设备的打开结构
if (retval)
return retval;
#ifdef CONFIG_UNIX98_PTYS
init_dev_done:
#endif
filp->private_data = tty; 将终端打开结构绑定到文件打开结构
file_move(filp, &tty->tty_files); 将文件打开结构与终端打开结构的文件表相环接
check_tty_count(tty, "tty_open");
if (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
    tty->driver.subtype == PTY_TYPE_MASTER)
noctty = 1;
#ifdef TTY_DEBUG_HANGUP
printk("opening %s...", tty_name(tty, buf));
#endif
if (tty->driver.open)
retval = tty->driver.open(tty, filp); 打开终端驱动设备
else
retval = -ENODEV;
filp->f_flags = saved_flags;
if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && !suser())
retval = -EBUSY;
if (retval) {
#ifdef TTY_DEBUG_HANGUP
printk("error %d in opening %s...", retval,
       tty_name(tty, buf));
#endif
release_dev(filp);
if (retval != -ERESTARTSYS)
return retval;
if (signal_pending(current))
return retval;
schedule();
/*
* Need to reset f_op in case a hangup happened.
*/
filp->f_op = &tty_fops;
goto retry_open;
}
if (!noctty &&
    current->leader &&
    !current->tty &&
    tty->session == 0) {
    task_lock(current);
current->tty = tty; 将打开终端绑定到进程上
task_unlock(current);
current->tty_old_pgrp = 0;
tty->session = current->session;
tty->pgrp = current->pgrp;
}
if ((tty->driver.type == TTY_DRIVER_TYPE_SERIAL) &&
    (tty->driver.subtype == SERIAL_TYPE_CALLOUT) &&
    (tty->count == 1)) {
static int nr_warns;
if (nr_warns pid, current->comm,
tty_name(tty, buf), TTY_NUMBER(tty));
nr_warns++;
}
}
return 0;
}
void file_move(struct file *file, struct list_head *list)
{
if (!list)
return;
file_list_lock();
list_del(&file->f_list);
list_add(&file->f_list, list);
file_list_unlock();
}
/*
* WSH 06/09/97: Rewritten to remove races and properly clean up after a
* failed open.  The new code protects the open with a semaphore, so it's
* really quite straightforward.  The semaphore locking can probably be
* relaxed for the (most common) case of reopening a tty.
*/
static int init_dev(kdev_t device, struct tty_struct **ret_tty)
{
struct tty_struct *tty, *o_tty;
struct termios *tp, **tp_loc, *o_tp, **o_tp_loc;
struct termios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc;
struct tty_driver *driver;
int retval=0;
int idx;
driver = get_tty_driver(device); 取终端驱动设备
if (!driver)
return -ENODEV;
idx = MINOR(device) - driver->minor_start; 计算所要驱动设备的索引
/*
* Check whether we need to acquire the tty semaphore to avoid
* race conditions.  For now, play it safe.
*/
down_tty_sem(idx);
/* check whether we're reopening an existing tty */
tty = driver->table[idx]; 从驱动程序子设备表中取设备打开指针
if (tty) goto fast_track; 如果该设备已被打开
/*
* First time open is complex, especially for PTY devices.
* This code guarantees that either everything succeeds and the
* TTY is ready for operation, or else the table slots are vacated
* and the allocated memory released.  (Except that the termios
* and locked termios may be retained.)
*/
o_tty = NULL;
tp = o_tp = NULL;
ltp = o_ltp = NULL;
tty = alloc_tty_struct();
if(!tty)
goto fail_no_mem;
initialize_tty_struct(tty);
tty->device = device;
tty->driver = *driver; 将终端驱动结构完整的拷贝到终端打开结构中
tp_loc = &driver->termios[idx];
if (!*tp_loc) {
tp = (struct termios *) kmalloc(sizeof(struct termios),
GFP_KERNEL);
if (!tp)
goto free_mem_out;
*tp = driver->init_termios;
}
ltp_loc = &driver->termios_locked[idx];
if (!*ltp_loc) {
ltp = (struct termios *) kmalloc(sizeof(struct termios),
GFP_KERNEL);
if (!ltp)
goto free_mem_out;
memset(ltp, 0, sizeof(struct termios));
}
if (driver->type == TTY_DRIVER_TYPE_PTY) {
o_tty = alloc_tty_struct(); 建立对端伪终端设备的打开结构
if (!o_tty)
goto free_mem_out;
initialize_tty_struct(o_tty);
o_tty->device = (kdev_t) MKDEV(driver->other->major,
driver->other->minor_start + idx);
o_tty->driver = *driver->other;
o_tp_loc  = &driver->other->termios[idx];
if (!*o_tp_loc) {
o_tp = (struct termios *)
kmalloc(sizeof(struct termios), GFP_KERNEL);
if (!o_tp)
goto free_mem_out;
*o_tp = driver->other->init_termios;
}
o_ltp_loc = &driver->other->termios_locked[idx];
if (!*o_ltp_loc) {
o_ltp = (struct termios *)
kmalloc(sizeof(struct termios), GFP_KERNEL);
if (!o_ltp)
goto free_mem_out;
memset(o_ltp, 0, sizeof(struct termios));
}
/*
* Everything allocated ... set up the o_tty structure.
*/
driver->other->table[idx] = o_tty;
if (!*o_tp_loc)
*o_tp_loc = o_tp;
if (!*o_ltp_loc)
*o_ltp_loc = o_ltp;
o_tty->termios = *o_tp_loc;
o_tty->termios_locked = *o_ltp_loc;
(*driver->other->refcount)++;
if (driver->subtype == PTY_TYPE_MASTER)
o_tty->count++;
/* Establish the links in both directions */
tty->link   = o_tty;
o_tty->link = tty;
}
/*
* All structures have been allocated, so now we install them.
* Failures after this point use release_mem to clean up, so
* there's no need to null out the local pointers.
*/
driver->table[idx] = tty;
if (!*tp_loc)
*tp_loc = tp;
if (!*ltp_loc)
*ltp_loc = ltp;
tty->termios = *tp_loc;
tty->termios_locked = *ltp_loc;
(*driver->refcount)++;
tty->count++;
/*
* Structures all installed ... call the ldisc open routines.
* If we fail here just call release_mem to clean up.  No need
* to decrement the use counts, as release_mem doesn't care.
*/
if (tty->ldisc.open) {
retval = (tty->ldisc.open)(tty);
if (retval)
goto release_mem_out;
}
if (o_tty && o_tty->ldisc.open) {
retval = (o_tty->ldisc.open)(o_tty);
if (retval) {
if (tty->ldisc.close)
(tty->ldisc.close)(tty);
goto release_mem_out;
}
}
goto success;
/*
* This fast open can be used if the tty is already open.
* No memory is allocated, and the only failures are from
* attempting to open a closing tty or attempting multiple
* opens on a pty master.
*/
fast_track:
if (test_bit(TTY_CLOSING, &tty->flags)) {
retval = -EIO;
goto end_init;
}
if (driver->type == TTY_DRIVER_TYPE_PTY &&
    driver->subtype == PTY_TYPE_MASTER) {
/*
* special case for PTY masters: only one open permitted,
* and the slave side open count is incremented as well.
*/
if (tty->count) {
retval = -EIO;
goto end_init;
}
tty->link->count++;
}
tty->count++;
tty->driver = *driver; /* N.B. why do this every time?? */
success:
*ret_tty = tty;
/* All paths come through here to release the semaphore */
end_init:
up_tty_sem(idx);
return retval;
/* Release locally allocated memory ... nothing placed in slots */
free_mem_out:
if (o_tp)
kfree(o_tp);
if (o_tty)
free_tty_struct(o_tty);
if (ltp)
kfree(ltp);
if (tp)
kfree(tp);
free_tty_struct(tty);
fail_no_mem:
retval = -ENOMEM;
goto end_init;
/* call the tty release_mem routine to clean out this slot */
release_mem_out:
printk("init_dev: ldisc open failed, clearing slot %d\n", idx);
release_mem(tty, idx);
goto end_init;
}
/*
* This routine returns a tty driver structure, given a device number
*/
struct tty_driver *get_tty_driver(kdev_t device)
{
int major, minor;
struct tty_driver *p;
minor = MINOR(device);
major = MAJOR(device);
for (p = tty_drivers; p; p = p->next) {
if (p->major != major)
continue;
if (minor minor_start)
continue;
if (minor >= p->minor_start + p->num)
continue;
return p;
}
return NULL;
}
static inline struct tty_struct *alloc_tty_struct(void)
{
struct tty_struct *tty;
if (PAGE_SIZE > 8192) {
tty = kmalloc(sizeof(struct tty_struct), GFP_KERNEL);
if (tty)
memset(tty, 0, sizeof(struct tty_struct));
} else
tty = (struct tty_struct *)get_zeroed_page(GFP_KERNEL);
return tty;
}
/*
* This subroutine initializes a tty structure.
*/
static void initialize_tty_struct(struct tty_struct *tty)
{
memset(tty, 0, sizeof(struct tty_struct));
tty->magic = TTY_MAGIC;
tty->ldisc = ldiscs[N_TTY]; 完整拷贝终端规程结构
tty->pgrp = -1;
tty->flip.char_buf_ptr = tty->flip.char_buf; 设置轮换字符缓冲区
tty->flip.flag_buf_ptr = tty->flip.flag_buf; 设置轮换标志缓冲区
tty->flip.tqueue.routine = flush_to_ldisc; 输出到线路规程进行处理
tty->flip.tqueue.data = tty;
init_MUTEX(&tty->flip.pty_sem);
init_waitqueue_head(&tty->write_wait);
init_waitqueue_head(&tty->read_wait);
tty->tq_hangup.routine = do_tty_hangup;
tty->tq_hangup.data = tty;
sema_init(&tty->atomic_read, 1);
sema_init(&tty->atomic_write, 1);
spin_lock_init(&tty->read_lock);
INIT_LIST_HEAD(&tty->tty_files);
}
/*
* 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);
}
static int check_tty_count(struct tty_struct *tty, const char *routine)
{
#ifdef CHECK_TTY_COUNT
struct list_head *p;
int count = 0;
file_list_lock();
for(p = tty->tty_files.next; p != &tty->tty_files; p = p->next) {
if(list_entry(p, struct file, f_list)->private_data == tty)
count++;
}
file_list_unlock();
if (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
    tty->driver.subtype == PTY_TYPE_SLAVE &&
    tty->link && tty->link->count)
count++;
if (tty->count != count) {
printk("Warning: dev (%s) tty->count(%d) != #fd's(%d) in %s\n",
       kdevname(tty->device), tty->count, count, routine);
return count;
       }
#endif
return 0;
}
static int tty_release(struct inode * inode, struct file * filp)
{
lock_kernel();
release_dev(filp);
unlock_kernel();
return 0;
}
/*
* Even releasing the tty structures is a tricky business.. We have
* to be very careful that the structures are all released at the
* same time, as interrupts might otherwise get the wrong pointers.
*
* WSH 09/09/97: rewritten to avoid some nasty race conditions that could
* lead to double frees or releasing memory still in use.
*/
static void release_dev(struct file * filp)
{
struct tty_struct *tty, *o_tty;
int pty_master, tty_closing, o_tty_closing, do_sleep;
int idx;
char buf[64];
tty = (struct tty_struct *)filp->private_data;
if (tty_paranoia_check(tty, filp->f_dentry->d_inode->i_rdev, "release_dev"))
return;
check_tty_count(tty, "release_dev");
tty_fasync(-1, filp, 0);
idx = MINOR(tty->device) - tty->driver.minor_start;
pty_master = (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
      tty->driver.subtype == PTY_TYPE_MASTER);
o_tty = tty->link;
#ifdef TTY_PARANOIA_CHECK
if (idx = tty->driver.num) {
printk("release_dev: bad idx when trying to free (%s)\n",
       kdevname(tty->device));
return;
}
if (tty != tty->driver.table[idx]) {
printk("release_dev: driver.table[%d] not tty for (%s)\n",
       idx, kdevname(tty->device));
return;
}
if (tty->termios != tty->driver.termios[idx]) {
printk("release_dev: driver.termios[%d] not termios "
       "for (%s)\n",
       idx, kdevname(tty->device));
return;
}
if (tty->termios_locked != tty->driver.termios_locked[idx]) {
printk("release_dev: driver.termios_locked[%d] not "
       "termios_locked for (%s)\n",
       idx, kdevname(tty->device));
return;
}
#endif
#ifdef TTY_DEBUG_HANGUP
printk("release_dev of %s (tty count=%d)...", tty_name(tty, buf),
       tty->count);
#endif
#ifdef TTY_PARANOIA_CHECK
if (tty->driver.other) {
if (o_tty != tty->driver.other->table[idx]) {
printk("release_dev: other->table[%d] not o_tty for ("
       "%s)\n",
       idx, kdevname(tty->device));
return;
}
if (o_tty->termios != tty->driver.other->termios[idx]) {
printk("release_dev: other->termios[%d] not o_termios "
       "for (%s)\n",
       idx, kdevname(tty->device));
return;
}
if (o_tty->termios_locked !=
      tty->driver.other->termios_locked[idx]) {
printk("release_dev: other->termios_locked[%d] not "
       "o_termios_locked for (%s)\n",
       idx, kdevname(tty->device));
return;
}
if (o_tty->link != tty) {
printk("release_dev: bad pty pointers\n");
return;
}
}
#endif
if (tty->driver.close)
tty->driver.close(tty, filp);
/*
* Sanity check: if tty->count is going to zero, there shouldn't be
* any waiters on tty->read_wait or tty->write_wait.  We test the
* wait queues and kick everyone out _before_ actually starting to
* close.  This ensures that we won't block while releasing the tty
* structure.
*
* The test for the o_tty closing is necessary, since the master and
* slave sides may close in any order.  If the slave side closes out
* first, its count will be one, since the master side holds an open.
* Thus this test wouldn't be triggered at the time the slave closes,
* so we do it now.
*
* Note that it's possible for the tty to be opened again while we're
* flushing out waiters.  By recalculating the closing flags before
* each iteration we avoid any problems.
*/
while (1) {
tty_closing = tty->count count read_wait)) {
wake_up(&tty->read_wait);
do_sleep++;
}
if (waitqueue_active(&tty->write_wait)) {
wake_up(&tty->write_wait);
do_sleep++;
}
}
if (o_tty_closing) {
if (waitqueue_active(&o_tty->read_wait)) {
wake_up(&o_tty->read_wait);
do_sleep++;
}
if (waitqueue_active(&o_tty->write_wait)) {
wake_up(&o_tty->write_wait);
do_sleep++;
}
}
if (!do_sleep)
break;
printk("release_dev: %s: read/write wait queue active!\n",
       tty_name(tty, buf));
schedule();
}
/*
* The closing flags are now consistent with the open counts on
* both sides, and we've completed the last operation that could
* block, so it's safe to proceed with closing.
*/
if (pty_master) {
if (--o_tty->count count, tty_name(o_tty, buf));
o_tty->count = 0;
}
}
if (--tty->count count (%d) for %s\n",
       tty->count, tty_name(tty, buf));
tty->count = 0;
}
/*
* We've decremented tty->count, so we should zero out
* filp->private_data, to break the link between the tty and
* the file descriptor.  Otherwise if filp_close() blocks before
* the the file descriptor is removed from the inuse_filp
* list, check_tty_count() could observe a discrepancy and
* printk a warning message to the user.
*/
filp->private_data = 0;
/*
* Perform some housekeeping before deciding whether to return.
*
* Set the TTY_CLOSING flag if this was the last open.  In the
* case of a pty we may have to wait around for the other side
* to close, and TTY_CLOSING makes sure we can't be reopened.
*/
if(tty_closing)
set_bit(TTY_CLOSING, &tty->flags);
if(o_tty_closing)
set_bit(TTY_CLOSING, &o_tty->flags);
/*
* If _either_ side is closing, make sure there aren't any
* processes that still think tty or o_tty is their controlling
* tty.  Also, clear redirect if it points to either tty.
*/
if (tty_closing || o_tty_closing) {
struct task_struct *p;
read_lock(&tasklist_lock);
for_each_task(p) {
if (p->tty == tty || (o_tty && p->tty == o_tty))
p->tty = NULL;
}
read_unlock(&tasklist_lock);
if (redirect == tty || (o_tty && redirect == o_tty))
redirect = NULL;
}
/* check whether both sides are closing ... */
if (!tty_closing || (o_tty && !o_tty_closing))
return;
#ifdef TTY_DEBUG_HANGUP
printk("freeing tty structure...");
#endif
/*
* Shutdown the current line discipline, and reset it to N_TTY.
* N.B. why reset ldisc when we're releasing the memory??
*/
if (tty->ldisc.close)
(tty->ldisc.close)(tty);
tty->ldisc = ldiscs[N_TTY];
tty->termios->c_line = N_TTY;
if (o_tty) {
if (o_tty->ldisc.close)
(o_tty->ldisc.close)(o_tty);
o_tty->ldisc = ldiscs[N_TTY];
}
/*
* Make sure that the tty's task queue isn't activated.
*/
run_task_queue(&tq_timer);
flush_scheduled_tasks();
/*
* The release_mem function takes care of the details of clearing
* the slots and preserving the termios structure.
*/
release_mem(tty, idx);
}
/*
* Releases memory associated with a tty structure, and clears out the
* driver table slots.
*/
static void release_mem(struct tty_struct *tty, int idx)
{
struct tty_struct *o_tty;
struct termios *tp;
if ((o_tty = tty->link) != NULL) {
o_tty->driver.table[idx] = NULL;
if (o_tty->driver.flags & TTY_DRIVER_RESET_TERMIOS) {
tp = o_tty->driver.termios[idx];
o_tty->driver.termios[idx] = NULL;
kfree(tp);
}
o_tty->magic = 0;
(*o_tty->driver.refcount)--;
free_tty_struct(o_tty);
}
tty->driver.table[idx] = NULL;
if (tty->driver.flags & TTY_DRIVER_RESET_TERMIOS) {
tp = tty->driver.termios[idx];
tty->driver.termios[idx] = NULL;
kfree(tp);
}
tty->magic = 0;
(*tty->driver.refcount)--;
free_tty_struct(tty);
}
; driver/char/n_tty.c:
struct tty_ldisc tty_ldisc_N_TTY = { 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 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;
}
static inline unsigned char *alloc_buf(void)
{
unsigned char *p;
int prio = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
if (PAGE_SIZE != N_TTY_BUF_SIZE) {
p = kmalloc(N_TTY_BUF_SIZE, prio);
if (p)
memset(p, 0, N_TTY_BUF_SIZE);
} else
p = (unsigned char *)get_zeroed_page(prio);
return p;
}
/*
* 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);
}
/*
* 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_close(struct tty_struct *tty)
{
n_tty_flush_buffer(tty);
if (tty->read_buf) {
free_buf(tty->read_buf);
tty->read_buf = 0;
}
}
/*
* Flush the input buffer
*/
void n_tty_flush_buffer(struct tty_struct * tty)
{
/* clear everything and unthrottle the driver */
reset_buffer_flags(tty);
if (!tty->link)
return;
if (tty->link->packet) {
tty->ctrl_status |= TIOCPKT_FLUSHREAD;
wake_up_interruptible(&tty->link->read_wait);
}
}
static inline void free_buf(unsigned char *buf)
{
if (PAGE_SIZE != N_TTY_BUF_SIZE)
kfree(buf);
else
free_page((unsigned long) buf);
}


原创粉丝点击