UNIX 98伪终端核心系统调用及实践实现

1. UNIX 98伪终端核心系统调用

以下为伪终端的工作模式,伪终端即伪终端对,分为伪终端主设备与伪终端从设备。主设备向用户显示信息或读取输入,从设备与作业进程交互。
驱动程序介于两者之间,维护有输入队列(主设备->从设备)与输出队列(从设备->主设备)。回显指的是将输入队列的数据添加至输出队列,这里的行缓冲(不同于stdio库的行缓冲)指输入队列出现换行符才将输入队列的内容发给从设备供前台进程组读取。

伪终端导图.jpg
伪终端导图.jpg

1.1 posix_openpt

功能描述

打开伪终端主设备(PTY Master),返回主设备文件描述符。

函数声明

#include <stdlib.h>
#include <fcntl.h>

int posix_openpt(int flags);

参数详解

  • flags:打开模式标志位(按位或组合):

    • O_RDWR:必须指定的读写模式
    • O_NOCTTY:防止成为控制终端
    • O_CLOEXEC:设置close-on-exec标志

返回值

  • 成功:返回主设备文件描述符(≥3)
  • 失败:返回-1,设置errno:

    • ENOMEM:内核内存不足
    • ENOSPC:伪终端资源耗尽
    • EMFILE:进程文件描述符达上限

1.2 grantpt

功能描述

设置从设备(PTY Slave)的权限为可访问。

函数声明

#include <stdlib.h>

int grantpt(int master_fd);

参数详解

  • master_fd:由posix_openpt返回的主设备描述符

返回值

  • 成功:返回0
  • 失败:返回-1,设置errno:

    • EINVAL:无效文件描述符
    • EACCES:权限不足

1.3 unlockpt

功能描述

解除主设备的访问锁,允许从设备被打开。

函数声明

#include <stdlib.h>

int unlockpt(int master_fd);

参数详解

  • master_fd:已通过grantpt授权的主设备描述符

返回值

  • 成功:返回0
  • 失败:返回-1,设置errno:

    • EINVAL:无效文件描述符

1.4 ptsname

功能描述

获取伪终端从设备路径名。

函数声明

#include <stdlib.h>

char *ptsname(int master_fd);

参数详解

  • master_fd:已解锁的主设备描述符

返回值

  • 成功:返回从设备路径字符串指针(如/dev/pts/5
  • 失败:返回NULL,设置errno:

    • EINVAL:无效文件描述符

2. ptyMasterOpen函数实现

功能目标

打开伪终端主设备并完成初始化配置,返回从设备路径。

函数原型

#include <fcntl.h>
#include <string.h>
#include <errno.h>

int ptyMasterOpen(char *slaveName, size_t snLen) {
    int master_fd;
    char *slave_path;
    
    master_fd = posix_openpt(O_RDWR | O_NOCTTY);
    if (master_fd == -1) return -1;

    if (grantpt(master_fd) == -1) {
        close(master_fd);
        return -1;
    }

    if (unlockpt(master_fd) == -1) {
        close(master_fd);
        return -1;
    }

    slave_path = ptsname(master_fd);
    if (slave_path == NULL) {
        close(master_fd);
        errno = EINVAL;
        return -1;
    }

    if (strlen(slave_path) >= snLen) {
        close(master_fd);
        errno = EOVERFLOW;
        return -1;
    }

    strncpy(slaveName, slave_path, snLen);
    return master_fd;
}

关键处理逻辑

  • 缓冲区验证:通过strlen校验目标缓冲区容量
  • 错误处理链:确保资源泄露防护
  • 标志位组合:O_RDWR|O_NOCTTY保证基本功能

3. ptyFork函数实现

3.1 核心数据结构

termios结构体(终端属性)

struct termios {
    tcflag_t c_iflag;  // 输入模式标志
    tcflag_t c_oflag;  // 输出模式标志
    tcflag_t c_cflag;  // 控制模式标志
    tcflag_t c_lflag;  // 本地模式标志
    cc_t c_cc[NCCS];   // 控制字符数组
};

winsize结构体(窗口尺寸)

struct winsize {
    unsigned short ws_row;    // 行数
    unsigned short ws_col;    // 列数
    unsigned short ws_xpixel; // 水平像素
    unsigned short ws_ypixel; // 垂直像素
};

3.2 函数实现

#include <unistd.h>
#include <sys/ioctl.h>

pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen,
              const struct termios *slaveTermios,
              const struct winsize *slaveWS) {
    int mfd, saved_errno;
    pid_t childPid;

    mfd = ptyMasterOpen(slaveName, snLen);
    if (mfd == -1) return -1;

    childPid = fork();
    if (childPid == -1) {
        saved_errno = errno;
        close(mfd);
        errno = saved_errno;
        return -1;
    }

    if (childPid != 0) {  // 父进程
        *masterFd = mfd;
        return childPid;
    }

    // 子进程执行流
    if (setsid() == -1) _exit(EXIT_FAILURE);

    close(mfd);

    int slave_fd = open(slaveName, O_RDWR);
    if (slave_fd == -1) _exit(EXIT_FAILURE);

#ifdef TIOCSCTTY
    if (ioctl(slave_fd, TIOCSCTTY, 0) == -1)
        _exit(EXIT_FAILURE);
#endif

    if (slaveTermios && tcsetattr(slave_fd, TCSANOW, slaveTermios) == -1)
        _exit(EXIT_FAILURE);

    if (slaveWS && ioctl(slave_fd, TIOCSWINSZ, slaveWS) == -1)
        _exit(EXIT_FAILURE);

    dup2(slave_fd, STDIN_FILENO);
    dup2(slave_fd, STDOUT_FILENO);
    dup2(slave_fd, STDERR_FILENO);

    if (slave_fd > STDERR_FILENO)
        close(slave_fd);

    return 0;
}

3.3 关键操作说明

  1. 会话控制

    • setsid():创建新会话,脱离原终端控制
    • TIOCSCTTY:显式获取控制终端(BSD兼容)
  2. 描述符重定向

    • dup2三步操作:实现标准流全重定向
  3. 属性继承

    • tcsetattr:精确复制原终端行为特征
    • TIOCSWINSZ:保持窗口尺寸一致性

4. 系统调用关系图谱

graph TD
    A[posix_openpt] --> B[grantpt]
    B --> C[unlockpt]
    C --> D[ptsname]
    D --> E[ptyMasterOpen]
    E --> F[fork]
    F --> G[setsid]
    G --> H[ioctl-TIOCSCTTY]
    H --> I[tcsetattr]
    I --> J[dup2]

通过系统调用的组合使用,可构建完整的伪终端通信通道,为高级终端应用开发奠定基础。