1. 虚拟终端概念

linux中有很多终端,如下简单介绍下各种终端或串口的概念。

1.1 tty:终端设备的统称

tty是Teletype或TeletypeWriter的缩写,中文翻译为电传打字机。电传打字机通常有键盘、收发报器和印字机等组成,是传真机使用以前的通信设备,原理近似电报。后被显示器和键盘所取代,所以现在叫终端比较合适。

终端是一种字符型设备,他有多种类型,通常使用tty来简称各种类型的终端设备。

目前,tty一般指控制终端(man 4 tty),设备文件是/dev/ttyx,常用的就是linux默认提供的6个命令行终端,可通过Ctrl+Alt+Fn切换图形界面或终端窗口。在Ubuntu命令行输入tty显示终端:



$ tty
/dev/tty2



1.2 pty:虚拟终端

A pseudoterminal缩写为pty,是虚拟终端或伪终端,可以在终端模拟器(terminal emulator)中运行,man pty查看。pty是成对的逻辑终端设备(即master和slave设备,对master的操作会反映到slave上,对slave的操作也会反映到master上),与实际物理设备无关。A pty is a pair of virtual character devices that provide a bidirectional communication channel. one end is called master; the other end is called the slave.

linux提供了两套虚拟终端接口,BSD-style和System V-style,System V-style终端也被称为UNIX 98 pseudoterminals,是目前使用的伪终端样式。

An unused UNIX 98 pseudoterminal master is opened by calling posix_openpt(3). (This function opens the master clone device, /dev/ptmx; see pts(4).) After performing any program-specific initializations, changing the ownership and permissions of the slave device using grantpt(3), and unlocking the slave using unlockpt(3)), the corresponding slave device can be opened by passing the name returned by ptsname(3) in a call to open(2).

PTM指pseudoterminal master,PTS指pseudoterminal slave。

/dev/ptmx (UNIX 98 master clone device),所有主设备对应的设备文件都指向/dev/ptmx

/dev/pts/* (UNIX 98 slave devices)

ssh或Telnet登录远程主机时的终端就是pty,运行tty查看:



$ tty
/dev/pts/11



伪终端是一对虚拟的字符设备,linux内核使用一种符合tty线规程(line discipline)的双向管道连接伪终端的主从设备。主设备上的任何写入操作都会反映到从设备上,反之亦然。从设备上的应用进程可以像使用传统终端一样读取来自主设备上应用程序的输入,以及向主设备应用输出信息。伪终端从设备应用通常是主设备应用的子进程,主应用打开一对伪终端并fork一个子进程,然后子进程打开并使用从设备。

1.3 串行端口终端

与计算机串行端口(RS-232)连接的终端设备,对应的设备文件名称为/dev/tty+类型+设备编号,如/dev/ttyS0,S表示设备类型,0为指定类型下的设备编号。这里的串行端口可以是通过硬件或软件模拟的,如USB转串口,虚拟串口。

2. 多个虚拟终端

Unix 98伪终端使用流程如下:

  • 使用posix_openpt打开master;
  • 使用grantpt设置调用进程为slave的属主并允许其对slave进行读写操作;
  • 使用unlockpt对slave解锁;
  • 使用ptsname返回slave的设备名;
  • 使用open打开slave设备并进行读写操作。

上述函数都来自glibc库。伪终端编程更常用的API是openpty,直接实现了上述流程的所有步骤。login_tty函数用于实现在指定的终端上启动登录会话。forkpty函数整合了openpty、fork和 login_tty,在网络服务程序可用于为新登录用户打开一对伪终端,并创建相应的会话子进程。

注意:使用opentty,login_pty和forkpty需要链接util库。



#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pty.h>
#include <utmp.h>
#include <errno.h>

#define SLAVE_DEV_NAME_MAX_LEN    128
#define PTY_BUFF_MAX_LEN        1024

int main(int argc, char *argv[])
{
    int mpty = 0, spty = 0;
    int rv = 0, n = 0;
    char spty_name[SLAVE_DEV_NAME_MAX_LEN]={0};
    char buf[PTY_BUFF_MAX_LEN] = {0};

    fd_set rfds;

    rv = openpty(&mpty, &spty, spty_name, NULL, NULL);
    if(-1 == rv){
        perror("Failed to get a pty");
        return -1;
    }

    printf("Get a pty pair, FD -- master[%d], slave[%d]\n", mpty, spty);
    printf("Slave name is %s\n", spty_name);

    FD_ZERO(&rfds);
    FD_SET(mpty, &rfds);

    while(1){
        rv = select(mpty+1, &rfds, NULL, NULL, NULL);
        if(rv < 0){
            perror("Failed to select");
            return -1;
        }

        if(FD_ISSET(mpty, &rfds)){
            n = read(mpty, buf, PTY_BUFF_MAX_LEN);
            if(n > 0){
            //    memset(buf+n, 0, PTY_BUFF_MAX_LEN-n);
                printf("recv [%d] bytes:[%s]\n", n, buf);
            } else if (n == 0){
                printf("Slave closed\n");
                break;
            } else {
                if(errno == EINTR){
                    continue;
                }
                perror("Failed to read the master\n");
                return -1;
            }
        }
    }

    close(mpty);
    close(spty);
    return 0;
}



编译及运行:



gcc pty_test.c -o pty_test -lutil -Wall
$ ./pty_test
Get a pty pair, FD -- master[3], slave[4]
Slave name is /dev/pts/6
recv [1] bytes:[1]
recv [11] bytes:[hello world]



另一终端:



$ echo -n "1" > /dev/pts/6
$ echo -n "hello world" > /dev/pts/6



每次运行上述程序,生成一个虚拟终端口(slave),由此同一主机可运行多个虚拟终端口(slave)。可通过文件/proc/sys/kernel/pty/max查询或修改伪终端数量。



$ cat /proc/sys/kernel/pty/max
4096



3. 远程访问串口

通过网络远程访问串口,首先需要把串口虚拟化网络端口,之后在网络中的另外一个主机上通过Telnet等工具直接访问该网络端口,或者把网络端口逆向为一个虚拟串口,进而通过minicom等工具进行访问。

socat工具可以实现上述功能。如本地(虚拟串口)/dev/pts6,主机IP:192.168.134.144,主机端口54321,对端主机虚拟串口文件tty.virt001,可通过如下步骤测试。

主机1串口转TCP端口:

sudo socat tcp-l:54321,reuseaddr,fork file:/dev/pts/6,waitlock=/var/run/ttypts.lock,clocal=1,cs8,nonblock=1,ixoff=0,ixon=0,ispeed=9600,ospeed=9600,raw,echo=0,crtscts=0

主机2TCP端口转虚拟串口:

sudo socat pty,link=/dev/tty.virt001 tcp:192.168.134.144:54321

主机2远程访问串口:

sudo minicom -D /dev/tty.virt001

telnet 192.168.134.144 54321

android studio虚拟串口例程_python

4. 虚拟终端双向收发

上述程序测试示例中由于ptm与pts在一个程序中,没有控制ptm的发送,不便于测试观察,网上有程序实现用两组虚拟终端中两个slave配对,从而基于串口的双向数据收发。



#!/usr/bin/env python3
#--coding = utf-8 --

import pty
import os 
import select

def mkpty():
    master1, slave = pty.openpty()
    slaveName1 = os.ttyname(slave)
    master2, slave = pty.openpty()
    slaveName2 = os.ttyname(slave)
    print ('\nslave device names: ', slaveName1, slaveName2)
    return master1, master2

if __name__ == "__main__":
    master1, master2 = mkpty()
    while True:
        rl, wl, el = select.select([master1, master2], [], [], 1)
        for master in rl:
            data = os.read(master, 128)
            print ("read %d data:" %len(data))
            if master == master1:
                os.write(master2, data)
            else :
                os.write(master1, data)



上述程序用python实现了两个虚拟终端slave双向收发。

android studio虚拟串口例程_串口_02

两个主机都可通过minicom双向收发数据。

测试中唯一不足是接收端对换行不能正确处理,可以回车但不能换行,可能与minicom设置有关,编程处理应该无问题。

此外注意到python程序是一个一个字符处理的,并没有按照换行符整行发送,不能正确换行可能也与python程序有关。

5. 常见的虚拟串口问题

1. linux下如何生成虚拟串口?

linux中有虚拟终端的概念即pty,pty是成对的逻辑终端设备(有两个终端组成,支持双向收发),linux系统调用原生支持生成虚拟终端。

无论是实体串口,还是虚拟串口,表现形式都是串口,在linux下都是通过termios访问设置的。Ubuntu下cutecom图形界面串口调试工具可以生成并测试虚拟串口。

windows下vspd软件(Virtual Serial Port Driver)可以生成并测试虚拟串口。

2. 串口的远程访问?

实体串口或虚拟串口(虚拟终端)要想实现远程访问,需要将串口数据转换到网络端口,远程通过网络实现远程访问串口。常用的工具ncat、socat都可以实现此功能。

3. 3G或4G无线模块怎样实现的多串口访问?

测试3G或4G模块时看到模块虚拟出多个串口,这是如何实现的呢?这些串口本身就是在模块内部的虚拟串口,通过USB载体表现出来。

此和上述的网络访问虚拟串口不同,这里的虚拟串口的访问借助USB协议,要求模块实现USB协议来表征模块本身实现的接口(多个虚拟串口)。

当主机访问3G或4G模块时,通过USB总线枚举来找到模块实现的串口功能;而3G或4G模块内部,多串口可能是虚拟串口,也可能是实体串口(芯片),但需要实现USB协议。