分类: LINUX
2009-09-11 11:12:33
-----------------------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://sjj0412.cublog.cn/
-----------------------------------------------------------
伪终端(Pseudo Terminal)是成对的逻辑终设备(即master和slave设备, 对master的操作会反映到slave上)。
例如/dev/ptyp3和/dev/ttyp3(或者在设备文件系统中分别是/dev/pty/m3和 /dev/pty/s3)。它们与实际物理设备并不直接相关。如果一个程序把ptyp3(master设备)看作是一个串行端口设备,则它对该端口的读/ 写操作会反映在该逻辑终端设备对应的另一个ttyp3(slave设备)上面。而ttyp3则是另一个程序用于读写操作的逻辑设备。
这样,两个程序就可以通过这种逻辑设备进行互相交流,而其中一个使用ttyp3的程序则认为自己正在与一个串行端口进行通信。这很象是逻辑设备对之间的管
道操作。对于ttyp3(s3),任何设计成使用一个串行端口设备的程序都可以使用该逻辑设备。但对于使用ptyp3的程序,则需要专门设计来使用 ptyp3(m3)逻辑设备。
例如,如果某人在网上使用telnet程序连接到你的计算机上,则telnet程序就可能会开始连接到设备
ptyp2(m2)上(一个伪终端端口上)。此时一个getty程序就应该运行在对应的ttyp2(s2)端口上。当telnet从远端获取了一个字符 时,该字符就会通过m2、s2传递给 getty程序,而getty程序就会通过s2、m2和telnet程序往网络上返回”login:”字符串信息。这样,登录程序与telnet程序就通 过“伪终端”进行通信。通过使用适当的软件,就可以把两个甚至多个伪终端设备连接到同一个物理串行端口上。
但是到了现在,/dev/pty/m不再存在,只是以file(/dev/ptmx的一次打开)存在,/dev/pty/s对应到了/dev/pts/x里。
故要创建伪终端,就必须打开/dev/ptmx文件。
一个典型的例子如下:
int main(){
int fdm fds;
char *slavename;
extern char *ptsname();
fdm = open("/dev/ptmx", O_RDWR);
/* open master */
grantpt(fdm);
/* change permission of slave */
unlockpt(fdm);
/* unlock slave */
slavename = ptsname(fdm);
/* get name of slave */
fds = open(slavename, O_RDWR);
/* open slave */
ioctl(fds,
I_PUSH, "ptem");
/* push ptem */
ioctl(fds,
I_PUSH, "ldterm");
/* push ldterm */
}
glib中的ptsname实现大致如下:
char* ptsname( int
fd )
{
unsigned int
pty_num;
static char
buff[64];
if (
ioctl( fd, TIOCGPTN, &pty_num ) != 0 )//最终调用上面的pty_unix98_ioctl获取当前ptmx主设备对应的pty从设备号.
return NULL;
snprintf( buff, sizeof(buff), "/dev/pts/%u",
pty_num );//格式化为/dev/pts/0,/dev/pts/1等,即:pts对应的文件全路径.
return buff;
}
这样就获得了pty的master,slave.master由打开/dev/ptmx得到file,slave打开/dev/pts/x得到file.
下面从源码角度分析伪终端机制:
首先看下/dev/ptmx的驱动:
static struct file_operations ptmx_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.ioctl = tty_ioctl,
.open = ptmx_open,
.release = tty_release,
.fasync = tty_fasync,
};
static int __init tty_init(void)
{
cdev_init(&ptmx_cdev,
&ptmx_fops);
if
(cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2),
1, "/dev/ptmx") < 0)
panic("Couldn't
register /dev/ptmx driver\n");
devfs_mk_cdev(MKDEV(TTYAUX_MAJOR,
2), S_IFCHR|S_IRUGO|S_IWUGO, "ptmx");
class_device_create(tty_class,
MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
}
这样当我们打开/dev/ptmx时就会执行ptmx_open:
static int ptmx_open(struct inode * inode, struct file * filp)
{
/* find a device that is
not in use. */
down(&allocated_ptys_lock);
if
(!idr_pre_get(&allocated_ptys, GFP_KERNEL)) {
up(&allocated_ptys_lock);
return -ENOMEM;
}
idr_ret
= idr_get_new(&allocated_ptys, NULL, &index);
//获得设备编号
if (idr_ret < 0) {
up(&allocated_ptys_lock);
if (idr_ret ==
-EAGAIN)
return
-ENOMEM;
return -EIO;
}
if (index >=
pty_limit) {
idr_remove(&allocated_ptys,
index);
up(&allocated_ptys_lock);
return -EIO;
}
up(&allocated_ptys_lock);
down(&tty_sem);
retval
= init_dev(ptm_driver, index, &tty);
//以index为pts的设备索引号,创建成对的主从设备ptmx和pts
up(&tty_sem);
if (retval)
goto out;
set_bit(TTY_PTY_LOCK,
&tty->flags); /* LOCK THE SLAVE */
filp->private_data =
tty;
file_move(filp,
&tty->tty_files);
retval = -ENOMEM;
if
(devpts_pty_new(tty->link))
goto out1;
check_tty_count(tty,
"tty_open");
retval =
ptm_driver->open(tty, filp);
static void initialize_tty_struct(struct
tty_struct *tty){
memset(tty, 0,
sizeof(struct tty_struct));
}
这个将tty和devpts绑定,并创建对应设备文件/dev/pts/x。
int devpts_pty_new(struct tty_struct *tty)
{
int
number = tty->index;
struct
tty_driver *driver = tty->driver;
dev_t
device = MKDEV(driver->major, driver->minor_start+number);
struct
dentry *dentry;
struct
inode *inode = new_inode(devpts_mnt->mnt_sb);
/*
We're supposed to be given the slave end of a pty */
BUG_ON(driver->type
!= TTY_DRIVER_TYPE_PTY);
BUG_ON(driver->subtype
!= PTY_TYPE_SLAVE);
if
(!inode)
return
-ENOMEM;
inode->i_ino
= number+2;
inode->i_blksize
= 1024;
inode->i_uid
= config.setuid ? config.uid : current->fsuid;
inode->i_gid
= config.setgid ? config.gid : current->fsgid;
inode->i_mtime
= inode->i_atime = inode->i_ctime = CURRENT_TIME;
init_special_inode(inode,
S_IFCHR|config.mode, device);
inode->i_op
= &devpts_file_inode_operations;
inode->u.generic_ip
= tty;
dentry
= get_node(number);
if
(!IS_ERR(dentry) && !dentry->d_inode)
d_instantiate(dentry,
inode);
up(&devpts_root->d_inode->i_sem);
return
0;
}
init_dev是初始化伪终端tty对:
static int init_dev(struct tty_driver *driver,
int idx,
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;
int
retval=0;
/*
check whether we're reopening an existing tty */
//查看是否已经存在此终端。
if
(driver->flags & TTY_DRIVER_DEVPTS_MEM) {
tty = devpts_get_tty(idx);//这个是从终端,所以要获的主终端。
if
(tty && driver->subtype == PTY_TYPE_MASTER)
tty
= tty->link;
}
else {
tty
= driver->ttys[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.)
*/
if
(!try_module_get(driver->owner)) {
retval
= -ENODEV;
goto
end_init;
}
tty = alloc_tty_struct();
//分配终端
if(!tty)
goto
fail_no_mem;
initialize_tty_struct(tty);
//初始化终端
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->driver
= driver->other;
o_tty->index
= idx;
tty_line_name(driver->other,
idx, o_tty->name);
if
(driver->flags & TTY_DRIVER_DEVPTS_MEM) {
o_tp_loc
= &o_tty->termios;
o_ltp_loc
= &o_tty->termios_locked;
}
else {
o_tp_loc
= &driver->other->termios[idx];
o_ltp_loc
= &driver->other->termios_locked[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;
}
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));
}
}
static void initialize_tty_struct(struct
tty_struct *tty)
{
memset(tty,
0, sizeof(struct tty_struct));
tty->magic
= TTY_MAGIC;
tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
//会赋值默认的行规则。
tty->pgrp
= -1;
tty->overrun_time
= jiffies;
tty->flip.char_buf_ptr
= tty->flip.char_buf;
tty->flip.flag_buf_ptr
= tty->flip.flag_buf;
INIT_WORK(&tty->flip.work,
flush_to_ldisc, tty);
init_MUTEX(&tty->flip.pty_sem);
init_MUTEX(&tty->termios_sem);
init_waitqueue_head(&tty->write_wait);
init_waitqueue_head(&tty->read_wait);
INIT_WORK(&tty->hangup_work,
do_tty_hangup, 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);
INIT_WORK(&tty->SAK_work,
NULL, NULL);
}
默认规则生成:
struct
tty_ldisc *tty_ldisc_get(int disc)
{
ld =
&tty_ldiscs[disc];
/*
Check the entry is defined */
return
ld;
}
tty_ldiscs是全局变量。
通过 tty_register_ldisc注册。
int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
{
tty_ldiscs[disc]
= *new_ldisc;
tty_ldiscs[disc].num
= disc;
tty_ldiscs[disc].flags
|= LDISC_FLAG_DEFINED;
tty_ldiscs[disc].refcount
= 0;
spin_unlock_irqrestore(&tty_ldisc_lock,
flags);
return
ret;
}
N_TTY是在console_int注册的:
void __init console_init(void)
{
initcall_t
*call;
/*
Setup the default TTY line discipline. */
(void)
tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
}
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 */
}
当对ptm或pts写时会执行tty_write(所有的tty设备文件的操作都会执行tty_ops的相应的函数,这个将在后面讲):
tty_write()
tty_write(struct inode *
inode, struct file * file, const char * buf, int count)
首先从file->private_data里面取出一个tty_struct结构,得到相关的tty的信息,
此函数中调用do_tty_write()
return do_tty_write(tty->ldisc.write,
inode, tty, file,
(const unsigned char *)buf,
(unsigned int)count);
do_tty_write(tty->ldisc.write,
tty, file,//最后调用do_tty_write()
注:大概考虑到一次不能写太多字符do_tty_write中将字符分批输出。
反复调用传来的write函数:ret = write(tty, file, buf, size);
因为默认是N_TTY行规则。
这个函数其实就是tty->ldisc.write,即write_chan()
static ssize_t
write_chan(struct tty_struct * tty, struct file * file,
const
unsigned char * buf, size_t nr)
{
/* Job control check -- must be done at start (POSIX.1
7.1.1.4). */
if (L_TOSTOP(tty) && file->f_op->write !=
redirected_tty_write) {
retval = tty_check_change(tty);
if (retval)
return retval;
}
add_wait_queue(&tty->write_wait, &wait);
while (1) {
set_current_state(TASK_INTERRUPTIBLE);
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (tty_hung_up_p(file) || (tty->link &&
!tty->link->count)) {
retval = -EIO;
break;
}
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT,
&tty->flags))) {
while (nr > 0) {
ssize_t num = opost_block(tty, b, nr);
if (num < 0) {
if (num == -EAGAIN)
break;
retval = num;
goto break_out;
}
b += num;
nr -= num;
if (nr == 0)
break;
c = *b;
if (opost(c, tty) < 0)
break;
b++; nr--;
}
if (tty->driver->flush_chars)
tty->driver->flush_chars(tty);
} else {
while (nr > 0) {
c =
tty->driver->write(tty, b, nr);
if (c < 0) {
retval = c;
goto break_out;
}
if (!c)
break;
b += c;
nr -= c;
}
}
if (!nr)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
schedule();
}
break_out:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&tty->write_wait, &wait);
return (b - buf) ? b - buf : retval;
}
然后就执行driver->write即
ptm的driver是ptm_driver,pts的driver是pts_driver,这两个都是在 unix98_pty_init初始化。
static void __init unix98_pty_init(void)
{
devfs_mk_dir("pts");
ptm_driver->init_termios.c_iflag = 0;
ptm_driver->init_termios.c_oflag = 0;
ptm_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
ptm_driver->init_termios.c_lflag = 0;
ptm_driver->flags = TTY_DRIVER_RESET_TERMIOS |
TTY_DRIVER_REAL_RAW |
TTY_DRIVER_NO_DEVFS | TTY_DRIVER_DEVPTS_MEM;
ptm_driver->other = pts_driver;
tty_set_operations(ptm_driver,
&pty_ops);
ptm_driver->ioctl =
pty_unix98_ioctl;
pts_driver->type = TTY_DRIVER_TYPE_PTY;
pts_driver->subtype = PTY_TYPE_SLAVE;
pts_driver->init_termios = tty_std_termios;
pts_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
pts_driver->flags = TTY_DRIVER_RESET_TERMIOS |
TTY_DRIVER_REAL_RAW |
TTY_DRIVER_NO_DEVFS | TTY_DRIVER_DEVPTS_MEM;
pts_driver->other = ptm_driver;
tty_set_operations(pts_driver,
&pty_ops);
if (tty_register_driver(ptm_driver))
panic("Couldn't register Unix98 ptm driver");
if (tty_register_driver(pts_driver))
panic("Couldn't register Unix98 pts driver");
}
static struct
tty_operations pty_ops = {
.open = pty_open,
.close = pty_close,
.write = pty_write,
.write_room = pty_write_room,
.flush_buffer = pty_flush_buffer,
.chars_in_buffer = pty_chars_in_buffer,
.unthrottle = pty_unthrottle,
.set_termios = pty_set_termios,
};
故执行driver->write就会执行pty_write
pty_write;
static int pty_write(struct tty_struct * tty, const unsigned
char *buf, int count)
{
struct tty_struct *to = tty->link;
int c;
if (!to ||
tty->stopped)
return 0;
c = to->ldisc.receive_room(to);
if (c > count)
c = count;
to->ldisc.receive_buf(to, buf, NULL, c);
//这个的效果是将数据放到终端队中另一个终端的read_Buf,就实现了虚拟设备的作用,即pts的输出会放到ptm的read_buf,ptm的输出会放到pts的read_buf。
return c;
}
当执行read时,调用tty_read
tty_read直接是调用N_TTY行规的read,是read_chan.
当对ptmx执行ioctl时:
会调用
int tty_ioctl(struct
inode * inode, struct file * file,
unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case TIOCSTI:
return tiocsti(tty, p);
case TIOCGWINSZ:
return tiocgwinsz(tty, p);
}
if (tty->driver->ioctl) {
retval =
(tty->driver->ioctl)(tty, file, cmd, arg);
if (retval != -ENOIOCTLCMD)
return retval;
}
ld = tty_ldisc_ref_wait(tty);
retval = -EINVAL;
if (ld->ioctl) {
retval = ld->ioctl(tty, file, cmd, arg);
if (retval == -ENOIOCTLCMD)
retval = -EINVAL;
}
}
然后就会执行drvier->ioctl,就是
pty_unix98_ioctl,
static int pty_unix98_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
return pty_set_lock(tty, (int __user *)arg);
case
TIOCGPTN: /* Get PT Number */
return
put_user(tty->index, (unsigned int __user *)arg);
//获取对应的pts编号。
}
}
当最tty设备文件进行操作时:file->f_ops=tty_fops
static struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.ioctl = tty_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
因为tty_regiser_driver会将tty字符设备的fops赋值为tty_fops:
int
tty_register_driver(struct tty_driver *driver)
{
cdev_init(&driver->cdev,
&tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
}
当执行close时:
asmlinkage long sys_close(unsigned int fd)
{
….....................
return filp_close(filp, files);
….......................
}
int filp_close(struct
file *filp, fl_owner_t id)
{
if (filp->f_op && filp->f_op->flush)
retval = filp->f_op->flush(filp);
fput(filp);
return retval;
}
void fastcall
__fput(struct file *file)
{
if (file->f_op && file->f_op->release)
file->f_op->release(inode,
file);
}
static int
tty_release(struct inode * inode, struct file * filp)
{
lock_kernel();
release_dev(filp);
unlock_kernel();
return 0;
}
static void
release_dev(struct file * filp)
{
if (tty->driver->close)
tty->driver->close(tty, filp);
}
static void
pty_close(struct tty_struct * tty, struct file * filp)
{
if (tty->driver->subtype == PTY_TYPE_MASTER) {
set_bit(TTY_OTHER_CLOSED, &tty->flags);
#ifdef CONFIG_UNIX98_PTYS
if (tty->driver == ptm_driver)
devpts_pty_kill(tty->index);
#endif
tty_vhangup(tty->link);
}
}
void devpts_pty_kill(int
number)
{
struct dentry *dentry = get_node(number);
if (!IS_ERR(dentry)) {
struct inode *inode = dentry->d_inode;
if (inode) {
inode->i_nlink--;
d_delete(dentry);
dput(dentry);
}
dput(dentry);
}
up(&devpts_root->d_inode->i_sem);
}
从上面可以看出,当关闭/dev/ptmx的file时,会关闭其对应的pts设备文件/dev/pts/x
一个伪终端应用telnetd的图例: