Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🐧 Linux Kernel Comments
    • Chapter 2 - 微型计算机组成结构

      • Chapter 2 - 微型计算机组成结构
    • Chapter 3 - 内核编程语言和环境

      • Chapter 3 - 内核编程语言和环境
    • Chapter 4 - 80X86 保护模式及其编程

      • Chapter 4.1-4.2 - 80X86 系统寄存器和系统指令 & 保护模式内存管理
      • Chapter 4.3 - 分段机制
      • Chapter 4.4 - 分页机制
      • Chapter 4.5 - 保护
      • Chapter 4.6 - 中断和异常处理
      • Chapter 4.7 - 任务管理
      • Chapter 4.8 - 保护模式编程初始化
      • Chapter 4.9 - 一个简单的多任务内核实例
    • Chapter 5 - Linux 内核体系结构

      • Chapter 5.1-5.2 - Linux 内核模式 & 体系结构
      • Chapter 5.3 - Linux 内核对内存的管理和使用
      • Chapter 5.4-5.6 - 中断机制 & 系统调用 & 系统时间和定时
      • Chapter 5.7-5.9 - Linux 进程控制 & 堆栈使用 & 文件系统
    • Chapter 6 - 引导启动程序 (boot)

      • Chapter 6 - 引导启动程序 (boot)
    • Chapter 7 - 初始化程序 (init)

      • Chapter 7 - 初始化程序 (init)
    • Chapter 8 - 内核代码

      • Chapter 8.1 - 内核代码总体功能
      • Chapter 8.2 - asm.s 程序
      • Chapter 8.3 - traps.c 程序
      • Chapter 8.4 - sys_call.s 程序
      • Chapter 8.5 - mktime.c 程序
      • Chapter 8.6 - sched.c 程序
      • Chapter 8.7 - signal.c 程序
      • Chapter 8.8 - exit.c 程序
      • Chapter 8.9 - fork.c 程序
      • Chapter 8.10 - sys.c 程序
    • Chapter 9 - 块设备驱动程序

      • Chapter 9.1 - 块设备驱动程序 总体功能
      • Chapter 9.2 - blk.h 文件
      • Chapter 9.3 - hd.c 程序
      • Chapter 9.4 - ll_rw_blk.c 程序
      • Chapter 9.5 - ramdisk.c 程序
    • Chapter 10 - 字符设备驱动程序

      • Chapter 10.1 - 字符设备驱动程序 总体功能
      • Chapter 10.2 - keyboard.S 程序
      • Chapter 10.3 - console.c 程序
      • Chapter 10.4 - serial.c 程序
      • Chapter 10.5 - rs_io.s 程序
      • Chapter 10.6 - tty_io.c 程序
      • Chapter 10.7 - tty_ioctl.c 程序
    • Chapter 12 - 文件系统

      • Chapter 12.1 - 文件系统 总体功能
      • Chapter 12.2 - buffer.c 程序
      • Chapter 12.3 - bitmap.c 程序
      • Chapter 12.4 - truncate.c 程序
      • Chapter 12.5 - inode.c 程序
      • Chapter 12.6 - super.c 程序
      • Chapter 12.7 - namei.c 程序
      • Chapter 12.9 - block_dev.c 程序
      • Chapter 12.10 - file_dev.c 程序
      • Chapter 12.11 - pipe.c 程序
      • Chapter 12.12 - char_dev.c 程序
      • Chapter 12.13 - read_write.c 程序
      • Chapter 12.14 - open.c 程序
      • Chapter 12.15 - exec.c 程序
      • Chapter 12.16 - stat.c 程序
      • Chapter 12.17 - fcntl.c 程序
      • Chapter 12.18 - ioctl.c 程序
      • Chapter 12.19 - select.c 程序

Chapter 10.7 - tty_ioctl.c 程序

Created by : Mr Dk.

2019 / 08 / 27 14:05

Ningbo, Zhejiang, China


10.7 tty_ioctl.c 程序

10.7.1 功能描述

实现了函数 tty_ioctl(),用于字符设备的控制操作。程序可以通过该函数修改指定终端 termios 结构体中的设置标志。该函数将由输入输出控制系统调用 sys_ioctl() 调用,用于实现基于 文件系统 的统一设备访问接口。一般用户程序不会直接使用 sys_ioctl(),而是:

  • 使用库函数中的封装函数
  • 使用 ioctl() 库函数

10.7.2 代码注释

change_speed() - 修改传输波特率

首先定义了串行端口使用的 波特率因子数组。

static unsigned short quotient[] = {
    0, 2304, 1536, 1047, 857,
    768, 576, 384, 192, 96,
    64, 48, 24, 12, 6, 3
};

在除数锁存标志 DLAB 置位的情况下,对串口的两个端口分别写入波特率因子的低字节和高字节。写完后复位 DLAB 位。

static void change_speed(struct tty_struct * tty)
{
    unsigned short port, quot;
    
    if (!(port = tty->read_q->data))
        // 不是串行终端,退出
        // 串行终端的 tty 结构读队列的 data 字段存放着串行端口的基址
        // 一般控制台终端的该字段为 0
        return;
    
    quot = quotient[tty->termios.c_cflag & CBAUD];
    
    cli();
    outb_p(0x80, port + 3); // DLAB 置位
    outb_p(quot & 0xff, port); // 波特率因子低字节
    outb_p(quot >> 8, port + 1); // 波特率因子高字节
    outb(0x03, port + 3); // DLAB 复位
    sti();
}

flush() - 刷新 tty 缓冲队列

令缓冲队列的头指针等于尾指针,从而清空缓冲区。

static void flush(struct tty_queue * queue)
{
    cli();
    queue->head = queue->tail;
    sti();
}

get_termios() / set_termios() - 读取/设置终端 termios 结构信息

static int get_termios(struct tty_struct * tty, struct termios * termios)
{
    int i;
    
    verify_area(termios, sizeof(*termios));
    for (i = 0; i < (sizeof(*termios)); i++)
        put_fs_byte( ((char *)&tty->termios)[i], i+(char *)termios );
    return 0;
}
static int set_termios(struct tty_struct * tty, struct termios * termios, int channel)
{
    int i, retsig;
    
    if ((current->tty == channel) && (tty->pgrp != current->pgrp)) {
        // 试图设置终端状态,但终端不在前台
        // 发送 SIGTTOU 信号
        retsig = tty_signal(SIGTTOU, tty);
        if (retsig == -ERESTARTSYS || retig == -EINTR)
            return retsig;
    }
    
    for (i = 0; i < (sizeof (*termios)); i++)
        ((char *) &tty->termios)[i] = get_fs_byte(i+(char *)termios);
    change_speed(tty); // 修改波特率
    return 0;
}

get_termio() / set_termios() - 读取/设置终端 termio 结构信息

termio 结构与 termios 基本相同,但标志集的数据类型不同。所以要经过类型转换。

static int get_termio(struct tty_struct * tty, struct termio * termio)
{
    int i;
    struct termio tmp_termio;
    
    verify_area(termio, sizeof(*termio));
    tmp_termio.c_iflag = tty->termios.c_iflag;
    tmp_termio.c_oflag = tty->termios.c_oflag;
    tmp_termio.c_cflag = tty->termios.c_cflag;
    tmp_termio.c_lflag = tty->termios.c_lflag;
    tmp_termio.c_line = tty->termios.c_line;
    
    for (i = 0; i < NCC; i++)
        tmp_termio.c_cc[i] = tty->termios.c_cc[i];
    for (i = 0; i < sizeof(*termio); i++)
        put_fs_byte(((char *)&tmp_termio)[i], i+(char *)termio);
    return 0;
}
static int set_termio(struct tty_struct * tty, struct termio * termio, int channel)
{
    int i, retsig;
    struct termio tmp_termio;
    
    if ((current->tty == channel) && (tty->pgrp != current->pgrp)) {
        // 当前进程不在前台
        // 发送 SIGTTOU 信号让使用终端的进程先暂时停止执行
        retsig = tty_signal(SIGTTOU, tty);
        if (retsig == -ERESTARTSYS || retsig == -EINTR)
            return retsig;
    }

    for (i = 0; i < (sizeof (*termio)); i++)
        ((char *)&tmp_termio)[i]=get_fs_byte(i+(char *)termio);
    
    *(unsigned short *)&tty->termios.c_iflag = tmp_termio.c_iflag;
    *(unsigned short *)&tty->termios.c_oflag = tmp_termio.c_oflag;
    *(unsigned short *)&tty->termios.c_cflag = tmp_termio.c_cflag;
    *(unsigned short *)&tty->termios.c_lflag = tmp_termio.c_lflag;
    tty->termios.c_line = tmp_termio.c_line;
    for(i = 0; i < NCC; i++)
        tty->termios.c_cc[i] = tmp_termio.c_cc[i];
    change_speed(tty);
    
    return 0;
}

tty_ioctl() - tty 终端设备输入输出控制函数

似乎与标准的 ioctl() 函数的参数相同:

  • int dev:设备号
  • int cmd:命令号
  • int arg:操作参数的指针

该函数首先根据参数中的设备号找出对应终端的 tty 结构,然后根据控制命令 cmd 分别进行处理。

int tty_ioctl(int dev, int cmd, int ary)
{
    struct tty_struct * tty;
    int pgrp;
    
    if (MAJOR(dev) == 5) {
        // 控制终端
        dev = current->tty;
        if (dev < 0)
            panic("tty_ioctl: dev<0");
    } else
        // 子设备号
        dev = MINOR(dev);
    
    // dev == 0,正在使用前台终端
    // 直接使用终端号 fg_console
    // dev > 0 - 虚拟终端 或 串行终端/伪终端
    tty = tty_table + (dev ? ((dev < 64) ? dev-1 : dev) : fg_console);
    
    switch (cmd) {
        case TCGETS:
            // 取终端 termios 结构体信息
            return get_termios(tty, (struct termios *) arg);
        case TCSETSF:
            // 设置 termios 结构体之前,清空读队列
            flush(tty->read_q); // 继续执行
        case TCSETWS:
            // 等待写队列中的数据处理完毕
            wait_until_sent(tty); // 继续执行
        case TCSETS:
            // 设置终端 termios 结构体
            return set_termios(tty, (struct termios *) arg, dev);
        case TCGETA:
            // 取终端的 termio 结构体
            return get_termio(tty, (struct termio *) arg);
        case TCSETAF:
            flush(tty->read_q); // 继续执行
        case TCSETAW:
            wait_until_sent(tty); // 继续执行
        case TCSETA:
            // 设置终端的 termio 结构体
            return set_termio(tty, (struct termio *) arg, dev);
        case TCSBRK:
            // 参数为 0,等待写队列处理完毕,并发送 break
            if (!arg) {
                wait_until_sent(tty);
                send_break(tty);
            }
            return 0;
        case TCXONC:
            // 开始/停止流控制
            switch (arg) {
                case TCOOFF:
                    // 挂起输出
                    tty->stopped = 1; // 停止终端输出
                    tty->write(tty); // 写缓冲队列输出
                    return 0;
                case TCOON:
                    // 恢复挂起的输出
                    tty->stopped = 0; // 恢复终端输出
                    tty->write(tty);
                    return 0;
                case TCIOFF:
                    // 终端停止输入
                    if (STOP_CHAR(tty))
                        // 放入 STOP 字符
                        PUTCH(STOP_CHAR(tty), tty->write_q);
                    return 0;
                case TCION:
                    if (START_CHAR(tty))
                        PUTCH(START_CHAR(tty), tty->write_q);
                    return 0;
            }
            return -INVAL; // 未实现
        case TCFLSH:
            // 刷新队列
            if (arg == 0)
                flush(tty->read_q);
            else if (arg == 1)
                flush(tty->write_q);
            else if (arg == 2) {
                flush(tty->read_q);
                flush(tty->write_q);
            } else
                return -EINVAL;
            return 0;
        case TIOCEXCL:
            return -EINVAL; // 未实现
        case TIOCNXCL:
            return -EINVAL; // 未实现
        case TIOCSCTTY:
            return -EINVAL; // 未实现
        case TSICGPGRP:
            // 读取前台进程组号
            verify_area((void *) arg, 4);
            put_fs_long(tty->pgrp, (unsigned long *) arg);
            return 0;
        case TIOCSPGRP:
            // 设置终端进程组号
            if ((current->tty < 0) ||
                (current->tty != dev) ||
                (tty->session != current->session))
                // 进程必须有控制终端
                return -ENOTTY;
            pgrp = get_fs_long((unsigned long *) arg);
            if (pgrp < 0)
                // 无效组号
                return -EINVAL;
            if (session_of_pgrp(pgrp) != current->session)
                // 会话与当前会话不同
                return -EPERM;
            tty->pgrp = pgrp;
            return 0;
        case TIOCOUTQ:
            // 返回写队列中还未送出的字符数
            verify_area((void *) arg, 4);
            put_fs_long(CHARS(tty->write_q), (unsigned long *) arg);
            return 0;
        case TIOCINQ:
            // 返回辅助队列中还未读取的字符数
            verify_area((void *) arg, 4);
            put_fs_long(CHARS(tty->secondary), (unsigned long *) arg);
            return 0;
        case TIOCSTI:
            // 模拟终端输入操作
            return -EINVAL; // 未实现
        case TIOCGWINSZ:
            // 读取终端设备的窗口大小信息
            return -EINVAL; // 未实现
        case TIOCSWINSZ:
            // 设置终端设备的窗口大小信息
            return -EINVAL; // 未实现
        // case ... 都未实现
        default:
            return -EINVAL;
    }
}
Edit this page on GitHub
Prev
Chapter 10.6 - tty_io.c 程序