Unix环境高级编程(二十)伪终端

1、综述

  伪终端对于一个应用程序而言,看上去像一个终端,但事实上伪终端并不是一个真正的终端。从内核角度看,伪终端看起来像一个双向管道,而事实上Solaris的伪终端就是用STREAMS构建的。伪终端总是成对地使用的,就好像是个管道的两端。一端的设备称为"主设备"(master),另一端的设备称为"从设备"(slave),每一对伪终端设备,例如/dev/ptys0和/dev/ttys0,就好像是通过一个管道连在一起,其"从设备"一端与普通的终端设备没有什么区别,而"主设备"一端则跟管道文件相似。

伪终端的用途:

(1)构造网络登录服务器,例如telnetd和rlogind服务器。

(2)script程序,将终端会话的所有输入和输出信息复制到一个文件中,自己置于终端和登录shell的一个新调用之间。

(3)expect程序,伪终端可以在非交互模式中驱动交互程序的运行

(4)运行协同进程

(5)观看长时间运行程序的输出

2、打开伪终端设备

  各种平台打开伪终端设备的方法有所不同,posix_openpt用来打开一个可用的伪终端主设备,该函数可移植的。伪终端从设备可被使用之前,必须设置它的权限,调用grantpt函数设置权限,使得应用程序可以访问它。unlockpt函数批准读伪终端从设备的访问,从而允许应用程序打开该设备。ptsname函数找到从伪终端设备的路径名。函数原型如下:

#include <stdlib.h>
#include <fcntl.h>
int posix_openpt(int oflag); //成功返回下一个可以用的PTY主设备的文件描述符,出错返回-1
int grantpt(int fildes); //更改从PTY设备的权限
int unlockpt(int fildes) //允许从PTY设备被打开
char *ptsname(int fildes); //成功返回指向PTY从设备名的指针,出错返回NULL

写个程序调用以上函数获取一个可用的PTY主设备描述符,然后获取该主设备的从伪终端的路径名。程序如下:

复制代码
 1 #define _XOPEN_SOURCE
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <fcntl.h>
 6 
 7 int main()
 8 {
 9     int masterfd,slavefd;
10     char *slavedevice;
11     if((masterfd = posix_openpt(O_RDWR|O_NOCTTY)) == -1)
12     {
13         perror("posix_openpt() error");
14         exit(-1);
15     }
16     if(grantpt(masterfd) == -1)
17     {
18         perror("grantpt() error");
19         exit(-1);
20     }
21     if(unlockpt(masterfd) == -1)
22     {
23         perror("unlockpt() error");
24         exit(-1);
25     }
26     if((slavedevice=ptsname(masterfd)) == NULL)
27     {
28         perror("ptsname() error");
29         exit(-1);
30     }
31     printf("slave device is: %s\n", slavedevice);
32     exit(0);
33 }
复制代码

程序执行结果如下:

 打开一个终端,输入tty 这个命令来查看当前所使用的终端名。参考自http://blog.csdn.net/heron804/article/details/8144103 

介绍另外两个函数ptym_open和ptys_open,前者用于打开下一个可用的PTY主设备,后者打开相应的从设备。这两个函数需要自己实现,函数声明如下所示:

int ptym_open(char *pts_name,int pts_namesz);

int pyts_open(char *pts_name);

3、基于STREAMS的伪终端

  基于STREAMS的PTY主克隆设备是/dev/ptmx。打开该设备,其克隆open例程自动决定第一个未被使用的PTY主设备,并打开这个设备。ptym_open和ptys_open实现如下所示:

 1 #define _XOPEN_SOURCE
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <fcntl.h>
 6 #include <stropts.h>
 7 #include <string.h>
 8 
 9 int ptym_open(char *pts_name,int pts_namesz)
10 {
11     char    *ptr;
12     int     fdm;
13     strncpy(pts_name,"/dev/ptmx",pts_namesz);
14     pts_name[pts_namesz-1] = '\0';
15     if((fdm = open(pts_name,O_RDWR)) < 0)
16         return -1;
17     if(grantpt(fdm) < 0)
18     {
19         close(fdm);
20         return -2;
21     }
22     if(unlockpt(fdm) < 0)
23     {
24         close(fdm);
25         return  -3;
26     }
27     if((ptr = ptsname(fdm)) == NULL)
28     {
29         close(fdm);
30         return -4;
31     }
32     strncpy(pts_name,ptr,pts_namesz);
33     pts_name[pts_namesz-1] = '\0';
34     return (fdm);
35 }
36 int ptys_open(char *pts_name)
37 {
38     int     fds,setup;
39     if((fds = open(pts_name,O_RDWR)) < 0)
40         return -5;
41     return fds;
42 }
43 int main()
44 {
45     int     fdm,fds;
46     char    slave_name[20];
47     fdm = ptym_open(slave_name,sizeof(slave_name));
48     if(fdm<0)
49     {
50         perror("ptym_open() error");
51         exit(-1);
52     }
53     printf("open master device successfully.\n");
54     printf("slave device name is:%s\n",slave_name);
55     fds = ptys_open(slave_name);
56     if(fds < 0)
57     {
58         perror("ptys_open() error");
59         exit(-1);
60     }
61     printf("open slave device successfully.\n");
62     exit(0);
63 }

测试结果如下:

 4、基于BSD的伪终端

   需要自己确定第一个可用的PTY主设备,主设备名为/dev/ptyAX(/dev/ptys0),这里的A表示16个字母"pqrstuvwxyQPRST"中的一个,X则为16个16进制数字(0~f)之一,这样一共可以256个伪终端主设备。从/dev/ptyp0开始不断尝试,直到成功打开一个可用的PTY主设备或试完了所有设备。ptym_open和ptys_open实现如下所示:

  1 #define _XOPEN_SOURCE
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <unistd.h>
  5 #include <fcntl.h>
  6 #include <stropts.h>
  7 #include <string.h>
  8 #include <errno.h>
  9 #include<grp.h>
 10 #include <sys/types.h>
 11 
 12 #ifndef _HAS_OPENPT
 13 int posix_openpt(int oflag)
 14 {
 15     int     fdm;
 16     char    *ptr1,*ptr2;
 17     char    ptm_name[16];
 18 
 19     strcpy(ptm_name,"/dev/ptyXY");
 20     for(ptr1="pqrstuvwxyzPQRST"; *ptr1 != 0; ptr1++)
 21     {
 22         ptm_name[8] = *ptr1;
 23         for(ptr2="0123456789abcdef";*ptr2 != 0;ptr2++)
 24         {
 25             ptm_name[9] = *ptr2;
 26             //try to open the master
 27             if((fdm = open(ptm_name,oflag)) < 0)
 28             {
 29                 if(errno == ENOENT)
 30                     return -1;
 31                 else
 32                     continue;
 33             }
 34             return fdm;
 35         }
 36     }
 37     errno = EAGAIN;
 38     return -1;
 39 }
 40 #endif
 41 
 42 #ifndef _HAS_PTSNAME
 43 char *ptsname(int fdm)
 44 {
 45     static char pts_name[16];
 46     char    *ptm_name;
 47     ptm_name = ttyname(fdm);
 48     if(ptm_name == NULL)
 49         return NULL;
 50     strncpy(pts_name,ptm_name,sizeof(pts_name));
 51     pts_name[sizeof(pts_name)-1] = '\0';
 52     if(strncmp(pts_name,"/dev/pty",9) == 0)
 53         pts_name[9] = 's';
 54     else
 55         pts_name[5] = 't';
 56     return pts_name;
 57 }
 58 #endif
 59 
 60 #ifndef _HAS_GRANTPT
 61 int grantpt(int fdm)
 62 {
 63     struct group    *grptr;
 64     int gid;
 65     char *pts_name;
 66 
 67     pts_name = ptsname(fdm);
 68     if((grptr = getgrnam("tty")) != NULL)
 69         gid = grptr->gr_gid;
 70     else
 71         gid = -1;
 72     if(chown(pts_name,getuid(),gid) < 0)
 73         return -1;
 74     return chmod(pts_name,S_IRUSR | S_IWUSR | S_IWGRP);
 75 }
 76 #endif
 77 
 78 #ifndef _HAS_UNLOCKPT
 79 int unlockput(int fdm)
 80 {
 81     return 0;
 82 }
 83 #endif
 84 int ptym_open(char *pts_name,int pts_namesz)
 85 {
 86     char    *ptr;
 87     int     fdm;
 88     strncpy(pts_name,"/dev/ptyXX",pts_namesz);
 89     pts_name[pts_namesz-1] = '\0';
 90     if((fdm = posix_openpt(O_RDWR)) < 0)
 91         return -1;
 92     if(grantpt(fdm) < 0)
 93     {
 94         close(fdm);
 95         return -2;
 96     }
 97     if(unlockput(fdm) < 0)
 98     {
 99         close(fdm);
100         return -3;
101     }
102     if((ptr = ptsname(fdm)) < 0)
103     {
104         close(fdm);
105         return -4;
106     }
107     strncpy(pts_name,ptr,pts_namesz);
108     pts_name[pts_namesz-1] = '\0';
109     return fdm;
110 }
111 int ptys_open(char *pts_name)
112 {
113     int     fds,setup;
114     if((fds = open(pts_name,O_RDWR)) < 0)
115         return -5;
116     return fds;
117 }

 5、基于Linux的伪终端

  Linux支持访问伪终端的BSD方法,也支持使用/dev/ptmx的克隆风格的伪终端接口。在Linux中PTY从设备以为组tty所拥有,ptym_open和ptys_open实现如下所示:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <fcntl.h>
  5 #include <stropts.h>
  6 #include <string.h>
  7 #include <errno.h>
  8 #include <sys/types.h>
  9 #include <asm/ioctl.h>
 10 
 11 #define TIOCGPTN    _IOR('T',0x30,  unsigned int) /* Get Pty Number (of pty-mux device) */
 12 #define TIOCSPTLCK    _IOW('T',0x31, int)  /* Lock/unlock Pty */
 13 
 14 #ifndef _HAS_OPENPT
 15 int posix_openpt(int oflag)
 16 {
 17     int     fdm;
 18     fdm = open("/dev/ptmx",oflag);
 19     return fdm;
 20 }
 21 #endif
 22 
 23 #ifndef _HAS_PTSNAME
 24 char *ptsname(int fdm)
 25 {
 26     static char     pts_name[16];
 27     int             sminor;
 28     if(ioctl(fdm,TIOCGPTN,&sminor) < 0)
 29         return NULL;
 30     snprintf(pts_name,sizeof(pts_name),"/dev/pts/%d",sminor);
 31     return pts_name;
 32 }
 33 #endif
 34 
 35 #ifndef _HAS_GRANTPT
 36 int grantpt(int fdm)
 37 {
 38     char *pts_name;
 39     pts_name = ptsname(fdm);
 40     return chmod(pts_name,S_IRUSR | S_IWUSR | S_IWGRP);
 41 }
 42 #endif
 43 
 44 #ifndef _HAS_UNLOCKPT
 45 int unlockput(int fdm)
 46 {
 47     int lock = 0;
 48     return (ioctl(fdm,TIOCSPTLCK,&lock));
 49 }
 50 #endif
 51 int ptym_open(char *pts_name,int pts_namesz)
 52 {
 53     char    *ptr;
 54     int     fdm;
 55     strncpy(pts_name,"/dev/ptmx",pts_namesz);
 56     pts_name[pts_namesz-1] = '\0';
 57     if((fdm = posix_openpt(O_RDWR)) < 0)
 58         return -1;
 59     if(grantpt(fdm) < 0)
 60     {
 61         close(fdm);
 62         return -2;
 63     }
 64     if(unlockput(fdm) < 0)
 65     {
 66         close(fdm);
 67         return -3;
 68     }
 69     if((ptr = ptsname(fdm)) < 0)
 70     {
 71         close(fdm);
 72         return -4;
 73     }
 74     strncpy(pts_name,ptr,pts_namesz);
 75     pts_name[pts_namesz-1] = '\0';
 76     return fdm;
 77 }
 78 int ptys_open(char *pts_name)
 79 {
 80     int     fds,setup;
 81     if((fds = open(pts_name,O_RDWR)) < 0)
 82         return -5;
 83     return fds;
 84 }
 85 int main()
 86 {
 87     int     fdm,fds;
 88     char    slave_name[20];
 89     fdm = ptym_open(slave_name,sizeof(slave_name));
 90     if(fdm<0)
 91     {
 92         perror("ptym_open() error");
 93         exit(-1);
 94     }
 95     printf("open master device successfully.\n");
 96     printf("slave device name is:%s\n",slave_name);
 97     fds = ptys_open(slave_name);
 98     if(fds < 0)
 99     {
100         perror("ptys_open() error");
101         exit(-1);
102     }
103     printf("open slave device successfully.\n");
104     exit(0);
105 }

执行结果如下所示:

 6、pty_fork函数

  函数功能:用fork调用打开主设备和从设备,创建作为会话首进程的子进程并使其具有控制终端。函数声明如下:

#include <termios.h>
#include <sys/ioctl.h>
pid_t ptt_fork(int *ptrfdm,char *slave_name,int slave_names,const struct termiosz *slave_termios,const struct winsize *slave_winsize);

函数实现如下所示:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <fcntl.h>
 5 #include <stropts.h>
 6 #include <string.h>
 7 #include <errno.h>
 8 #include <sys/types.h>
 9 #include <asm/ioctl.h>
10 #include <termios.h>
11 #ifndef IIOCGWINSZ
12 #include <sys/ioctl.h>
13 #endif
14 pid_t    ptt_fork(int *ptrfdm,char *slave_name,int slave_namesz,
15                const struct termios *slave_termios,
16                const struct winsize *slave_winsize)
17 {
18     int     fdm,fds;
19     pid_t   pid;
20     char    pts_name[20];
21 
22     if((fdm=ptym_open(pts_name,sizeof(pts_name))) < 0)
23     {
24         perror("ptym_open()error");
25         exit(-1);
26     }
27     if(slave_name != NULL)
28     {
29         strncpy(slave_name,pts_name,slave_namesz);
30         slave_name[slave_namesz-1] = '\0';
31     }
32     if((pid = fork()) < 0)
33     {
34         perror("fork() error");
35         exit(-1);
36     }
37     else if(pid == 0)
38     {
39         if(setsid() < 0)
40         {
41             perror("setsid() error");
42             exit(-1);
43         }
44         if((fds = ptys_open(pts_name)) < 0)
45         {
46             perror("ptys_open() error");
47             exit(-1);
48         }
49         close(fdm);
50     #if defined (TIOCSCTTY)
51         if(ioctl(fds,TIOCSCTTY,(char *)0) < 0)
52         {
53             perror("TIOCSCTTY error");
54             exit(-1);
55         }
56     #endif
57         if(slave_termios != NULL)
58         {
59             if(tcsetattr(fds,TCSANOW,slave_termios) < 0)
60             {
61                 perror("tcsetattr error on slave pty");
62                 exit(-1);
63             }
64         }
65         if(slave_winsize != NULL)
66         {
67             if(ioctl(fds,TIOCSWINSZ,slave_winsize) < 0)
68             {
69                 perror("TIOCSWINSZ error on slave pty");
70                 exit(-1);
71             }
72         }
73 
74         if(dup2(fds,STDIN_FILENO) != STDIN_FILENO)
75         {
76             perror("dups error to stdin");
77             exit(-1);
78         }
79         if(dup2(fds,STDOUT_FILENO) != STDOUT_FILENO)
80         {
81             perror("dups error to stdout");
82             exit(-1);
83         }
84         if(dup2(fds,STDERR_FILENO) != STDERR_FILENO)
85         {
86             perror("dups error to stderr");
87             exit(-1);
88         }
89         if(fds != STDIN_FILENO && fds != STDOUT_FILENO && fds != STDERR_FILENO)
90             close(fds);
91         return 0;
92     }
93     else
94     {
95         *ptrfdm = fdm;
96         return pid;
97     }
98 }

posted on 2018-02-24 15:57  AlanTu  阅读(1498)  评论(0编辑  收藏  举报

导航