本文系本站原创,欢迎转载!
转载请注明出处:http://sjj0412.cublog.cn/
-----------------------------------------------------------
Linux下伪终端是对应于/dev/pts/x,这个/dev/pts其实也只是devpts的挂载目录,通过如下命令挂在的。mount devpts /dev/pts -t devpts
伪终端(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