linux 伪终端机制,伪终端(pty)机制祥解

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(){

intfdm fds;char*slavename;externchar*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(intfd){unsignedintpty_num;staticcharbuff[64];if(ioctl(fd,TIOCGPTN,&pty_num)!=0)//最终调用上面的pty_unix98_ioctl获取当前ptmx主设备对应的pty从设备号.returnNULL;snprintf(buff,sizeof(buff),"/dev/pts/%u",pty_num);//格式化为/dev/pts/0,/dev/pts/1等,即:pts对应的文件全路径.returnbuff;}

这样就获得了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注册。

inttty_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 __initunix98_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;

intc;

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的图例:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值