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 }

Unix环境高级编程(二十)伪终端的更多相关文章

  1. Unix环境高级编程(二)文件和目录

    本章主要介绍的是文件结构及目录.重点是通过stat函数获取文件的结构信息,然后是文件目录及其遍历.学完本章后,编写了一个输出给的目录下的文件信息的程序. 首先是包含在<sys/stat.h> ...

  2. Unix环境高级编程(二十一)数据库函数库

    本章的内容是开发一个简单的.多用户数据库的C函数库.调用此函数库提供的C语言函数,其他程序可以读取和存储数据库中的记录.绝大部分商用数据库函数库提供多进程同时更新数据库所需要的并发控制,采用建议记录锁 ...

  3. (十二) 一起学 Unix 环境高级编程 (APUE) 之 进程间通信(IPC)

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  4. (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  5. (十) 一起学 Unix 环境高级编程 (APUE) 之 线程控制

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  6. [置顶] 文件和目录(二)--unix环境高级编程读书笔记

    在linux中,文件的相关信息都记录在stat这个结构体中,文件长度是记录在stat的st_size成员中.对于普通文件,其长度可以为0,目录的长度一般为1024的倍数,这与linux文件系统中blo ...

  7. Unix 环境高级编程

    UNIX 环境高级编程 本书描述了UNIX系统的程序设计接口--系统调用接口和标准C库提供的很多函数. 与大多数操作系统一样,Unix为程序员运行提供了大量的服务--打开文件,读文件,启动一个新程序, ...

  8. 《UNIX环境高级编程》(APUE) 笔记系列

    本系列笔记主要是对于 <UNIX环境高级编程>(APUE) 各章节内容 概念性的总结 ,不涉及代码解读 . 目录 : 第一章 UNIX基础知识 第二章 UNIX标准及实现 第三章 文件I/ ...

  9. (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. window.open()页面之间函数传值

    项目中遇到的问题,使用window.open()开一个页面之后,cookie会消失,所以无法一键切肤不管作用,解决方案如下: window.open()总结: window.open("sU ...

  2. android 百度地图demo 随感

    最近项目组的老大要我对百度的android的sdk进行一段的预研,由于技术太菜,出了不少的错误,因此有一点感悟了. 嗨,这个错误浪费了我一天的时间的时候,我按照百度的技术文档一步步的来做,每部基本上都 ...

  3. HDU OJ Digital Roots 题目1013

     /*Digital Roots Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Other ...

  4. 设置网站expires和max-age属性

    转:http://www.zicheng.net/article/982022.htm 在使用百度站长工具测试网站优化建议时,在 设置静态内容缓存时间 栏目里,会提示 类似 FAILED - (未设置 ...

  5. dobbo 服务配置详解(解决超时重试问题)

    <!-- reference method -->     <dubbo:reference interface="com.xx.XxxService">  ...

  6. 如何使用屏幕取色工具ColorPixl

    ColorPix可以屏幕取色,假如现在想要取色桌面徽标键的颜色,按任意键可以锁定这个区域(press any key to lock)这样我们就可以在放大的区域更清楚的取色,加号按钮可以设置该软件是否 ...

  7. swift第一章

    swift中添加的类型:Tuple(元组类型),能够让你创建或者传递一组数据. 比方作为函数的返回值时.你能够用一个元组能够返回多个值. swift中还添加了可选(Optional)类型,用于处理值缺 ...

  8. LoadRunner录制:集合点

    背景 LoadRunner 执行过程中,有的user 跑的快,有的跑的慢.就导致user1可能还在执行 登录操作呢,user2都已经开始执行查询操作了. 但是在进行负载测试时 ,我们又需要让很多用户同 ...

  9. 解决mysql下区分表名大小写的问题

    MySQL在Linux下采用 rpm方式安装后默认是: 数据库名与表名\表的别名\变量名是严格区分大小写 1.用root帐号登录,/etc/ mysql/my.cnf中的[mysqld]后添加lowe ...

  10. Emmet初探2

    关于Emmet Emmet插件的前身是Zen coding,可以大幅度提高前端开发效率的一个工具,也有人说类似于jade(高性能的模板引擎,它深受 Haml 影响,它是用 JavaScript 实现的 ...