Chinaunix首页 | 论坛 | 博客
  • 博客访问: 517578
  • 博文数量: 95
  • 博客积分: 5168
  • 博客等级: 大校
  • 技术积分: 1271
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-28 23:31
文章分类

全部博文(95)

文章存档

2013年(2)

2012年(3)

2011年(1)

2010年(8)

2009年(81)

分类: LINUX

2009-09-11 11:12:33

-----------------------------------------------------------
本文系本站原创,欢迎转载!

转载请注明出处:http://sjj0412.cublog.cn/

-----------------------------------------------------------   

    Linux下伪终端是对应于/dev/pts/x,这个/dev/pts其实也只是devpts的挂载目录,通过如下命令挂在的。mount devpts /dev/pts -t devpts

   伪终端(Pseudo Terminal)是成对的逻辑终设备(masterslave设备, 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从远端获取了一个字符 时,该字符就会通过m2s2传递给 getty程序,而getty程序就会通过s2m2telnet程序往网络上返回”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;
}

    这样就获得了ptymaster,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);

//indexpts的设备索引号,创建成对的主从设备ptmxpts

       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));

}

这个将ttydevpts绑定,并创建对应设备文件/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 */

当对ptmpts写时会执行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

ptmdriverptm_driver,ptsdriverpts_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的输出会放到ptmread_bufptm的输出会放到ptsread_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/ptmxfile时,会关闭其对应的pts设备文件/dev/pts/x

一个伪终端应用telnetd的图例:

 

 


阅读(4209) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~