免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3369 | 回复: 1
打印 上一主题 下一主题

伪终端(pty)机制祥解 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-09-11 11:12 |只看该作者 |倒序浏览

                -----------------------------------------------------------本文系本站原创,欢迎转载!
转载请注明出处: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")
              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
              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
                                   if (num == -EAGAIN)
                                          break;
                                   retval = num;
                                   goto break_out;
                            }
                            b += num;
                            nr -= num;
                            if (nr == 0)
                                   break;
                            c = *b;
                            if (opost(c, tty)
                                   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
                                   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的图例:



  
  
  
  
  
  
  
  
  
  
  
  






file:///C:/DOCUME~1/jerry/LOCALS~1/Temp/msohtmlclip1/01/clip_image002.jpg

               
               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/89957/showart_2050203.html

论坛徽章:
0
2 [报告]
发表于 2013-04-13 16:03 |只看该作者
本帖最后由 McZoden 于 2013-04-13 16:04 编辑

你的文章帮了我大忙,我在移植kernel时发现
linux-2.6.21无须mount devpts /dev/pts -t devpts,telnetd也可正常工作
但到了linux-2.6.36,必须先mount devpts,否则telnetd子进程在试图open /dev/pts/x时会回EIO的errno

谢谢!!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP