🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
ListTTysCmd和PppdCmd比较简单,但也涉及两个重要的背景知识。 **1.背景知识介绍** 本节介绍的两个背景知识点是TTY和ptmx编程,以及PPP和pppd。 (1)TTY和ptmx编程[19][20] TTY是Linux系统(更确切地说是UNIX)中终端设备的统称,该词源于TeleTYpewriter(电传打字机),是一个通过串行线用打印机键盘通过阅读和发送信息的设备。不过随着计算机技术的发展,这类设备早就被键盘和显示器替代了。 从现在的情况来看,Linux系统中的TTY设备包含许多类型的设备,它们大体可分为以下 三种。 - 串行端口终端:这类设备一般命名为/dev/ttySn,n为索引号,例如ttyS0、ttyS1等。它们类似于Windows系统的COM0、COM1等,代表串行端口。当某个应用往ttyS0写入数据时,另一个应用就能从ttyS0读到对应的数据。 - 伪终端(psuedo terminal):这类设备成对出现,其中一个是master设备,另一个是slave设备。二者的关系类似管道(Pipe),当一个程序往slave设备写数据时,另外一个打开master设备的程序就能读到数据。以前(UNIX 98 scheme之前)主从设备命名方式为/dev/ptyp0和/dev/ttyp0。现在master设备命名为/dev/ptmx,而slave设备则对应/dev/pts/目录下的文件名。 - 控制台终端:这类设备命名从/dev/ttyN(N从1~64)和/dev/console。/dev/tty0代表当前使用的终端,而/dev/console一般指向这个终端。例如初始化时登录系统用的是/dev/tty2,那么/dev/tty0指向/dev/tty2,如果切换到/dev/tty1,则/dev/tty0就指向/dev/tty1了。 通过adb shell登录手机时,其实使用的都是伪终端。图2-20所示为笔者用adb shell登录Galaxy Note 2后用tty命令打印的输出。 :-: ![](https://box.kancloud.cn/162054ac93f093bb0a37a815361503e8_549x161.jpg) 图2-20 tty命令 对程序员来说,Linux提供了针对伪终端的编程接口。伪终端在概念上和管道没有太大区别,但是在实际操作时有一些特殊API需要调用。 ~~~ 主从两端要包含头文件:一般是在两个有亲缘关系的父子进程中使用 #include<stdlib.h> 主端需要打开/dev/ptmx设备,得到一个文件描述符 从端在使用这个文件描述符前,需要调用grantpt以获取权限,然后调用unlockpt解锁 从端通过ptsname获得从端设备文件名 从端打开由ptsname得到的从端设备 ~~~ Netd中的logwrap将使用伪终端在父子进程中传递log信息。代码如下所示。 **logwrap.c** ~~~ int logwrap(int argc, const char* argv[]) { pid_t pid; int parent_ptty; int child_ptty; char child_devname[64]; // 父进程打开/dev/ptmx parent_ptty = open("/dev/ptmx", O_RDWR); ...... // 父进程解锁/dev/ptmx。ptsname_r将得到从端设备的名字,例如/dev/pts/1 if (grantpt(parent_ptty) || unlockpt(parent_ptty) || ptsname_r(parent_ptty, child_devname, sizeof(child_devname))) { // ptsname_r是ptsname的可重入(Reentrant,可用于多线程环境)版本 ......// 错误处理 } pid = fork(); if (pid < 0) { ......// 错误处理 } else if (pid == 0) { // 子进程打开从端设备 child_ptty = open(child_devname, O_RDWR); // 关闭从父进程那继承到的主设备文件 close(parent_ptty); dup2(child_ptty, 1);// 重定向标准输入、输出、错误输出到伪终端的从设备 dup2(child_ptty, 2); close(child_ptty); child(argc, argv); // 执行child函数 } else { int rc = parent(argv[0], parent_ptty); // 父进程执行parent函数 close(parent_ptty); return rc; } return 0; } ~~~ 为什么Netd要使用这种一般很少用的ptmx伪终端呢?结合代码中的注释,笔者认为原因有以下两个。 - 后文会介绍,Netd将fork很多子进程以执行各种任务,而这些进程的代码一般都直接来自于已有的开源代码。它们没有采用Android的ALOG宏来记录日志。所以,采用这种I/O重定向方法,这些进程的输出将全部传递到Netd进程中来。而Netd进程是可以使用ALOG宏来打印输出的。上述代码中的parent函数将一直读取/dev/ptmx的内容,然后通过ALOG宏输出。 - 使用伪终端来承担父子进程通信重任(还可以使用别的进程间通信手段例如socketpair、pipe等)的另一个原因是伪终端不会缓存数据。例如一旦子进程往从设备中写入数据(借助I/O重定向,当子进程往标准输出中写数据时,其实是往从设备中写数据),父进程就能读到它们。 (2)PPP和pppd[21][22][23][24] PPP(Point-to-Point Protocol,点对点协议)是为在同等单元之间传输数据包这样的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。设计目的主要是通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。 PPP提供了一整套方案来解决链路建立、维护、拆除、上层协议协商、认证等问题,它主要包含以下几个部分。 - 链路控制协议(Link Control Protocol,LCP):负责创建、维护或终止一次物理连接。 - 网络控制协议(Network Control Protocol,NCP):包含一簇协议,负责解决物理连接上运行什么网络协议,以及解决上层网络协议发生的问题。 - 认证协议:最常用的包括口令验证协议(Password Authentication Protocol,PAP)和挑战握手验证协议(Challenge-Handshake Authentication Protocol,CHAP)。使用时,客户端会将自己的身份发送给远端的接入服务器。期间将使用认证协议避免第三方窃取数据或冒充远程客户接管与客户端的连接。 pppd(PPP Daemon的缩写)是运行PPP协议的后台进程。它和Kernel中的PPP驱动联动,以完成在直连线路(DSL、拨号网络)上建立IP通信链路的工作。读者可简单认为有了pppd,就可以在直连线路上传输IP数据包(类似以前我国大部分家庭用Modem和电话线上网)。 **2.ListTTysCmd和PppdCmd命令分析** 这两个命令非常简单,ListTtysCmd用于枚举系统中所有的tty设备,这是通过列举/sys/class/tty目录中以tty开头的文件名来完成的。 PppdCmd仅有attach和detach两个选项。其中,attach用于启动pppd进程,而detach用于杀死pppd进程。代码在PppController的attachPppd和detachPppd函数中,请读者自行阅读。 PppController启动pppd时传递的启动参数如下所示。 ~~~ pppd-detach \#启动后转成后台进程 devname\ #使用devname这个设备上链接到另一端的设备 115200\ #波特率设置为115200 iplocal:ipremote\ #设置本地和远端IP ms-dns d1 \#为运行在windows的程序配置dns地址,第一个是主dns的地址 ms-dnsd2 \#第二个是从dns的地址 lcp-max-configure 99999 #这里设置LCP config request最大个数为99999 ~~~ 配置并启动pppd后,手机就相当于一个Modem了,是不是很神奇呢?