• Linux TTY介绍


    1. TTY介绍

    TTY(TeleType)指Linux中的一类终端(Terminal)设备, 是一种字符设备

    在Linux中, tty可分为如下几类
    - 串行端口终端(serial port terminal): 指使用计算机串行端口连接的终端设备, /dev/ttySn
    - 伪终端(pseudo terminal): 通常是通过ssh登陆的终端, /dev/pts/*
    - 控制终端(controlling terminal): 代表当前tty设备 /dev/tty
    - 控制台终端(console):  指计算机的输出设备, 通常是printk信息输出的设备, /dev/ttyn、/dev/console

    详细定义如下

    /* tty driver types */
    #define TTY_DRIVER_TYPE_SYSTEM         0x0001
    #define TTY_DRIVER_TYPE_CONSOLE        0x0002
    #define TTY_DRIVER_TYPE_SERIAL         0x0003
    #define TTY_DRIVER_TYPE_PTY            0x0004
    #define TTY_DRIVER_TYPE_SCC            0x0005    
    /* scc driver */
    #define TTY_DRIVER_TYPE_SYSCONS        0x0006

    tty可以分为如下几层
    - 核心层(tty core): 是tty设备的抽象
    - 线路规程(tty line discipline): 是对上层和底层之间数据传输的协议转换, 不同类型的终端设备数据转换协议不同
    - 驱动层(tty driver): 面向底层硬件的设备驱动

    tty代码位于drivers/tty目录下

    tty软件框架如图所示:

    tty_structure

    2. TTY初始化

    在系统启动过程中, 注册了/dev/tty和/dev/console设备

    chr_dev_init()
      tty_init()
        /* 注册/dev/tty设备 */
        cdev_init(&tty_cdev, &tty_fops);
        cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1)
        register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty")
        device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty")
        /* 注册/dev/console设备 */ 
    
        cdev_init(&console_cdev, &console_fops);
        cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1)
        register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console")
        consdev = device_create_with_groups(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL, cons_dev_groups, "console");
    
    start_kernel()
      console_init()
        /* 注册N_TTY线路规程 */
        n_tty_init()
          tty_register_ldisc(N_TTY, &n_tty_ops);

    其中, 值得一说的是tty_fops, 定义了tty设备文件操作集

    static const struct file_operations tty_fops = {
        .llseek            = no_llseek,
        .read              = tty_read,
        .write             = tty_write,
        .poll              = tty_poll,
        .unlocked_ioctl    = tty_ioctl,
        .compat_ioctl      = tty_compat_ioctl,
        .open              = tty_open,
        .release           = tty_release,
        .fasync            = tty_fasync,
    };

    3. TTY接口

    驱动相关API主要如下:

    /* 分配tty驱动数据结构 */
    struct tty_driver *alloc_tty_driver(unsigned int lines);
    struct tty_driver *tty_alloc_driver(unsigned int lines, unsigned long flags)
    /* 注册/释放tty驱动 */
    int tty_register_driver(struct tty_driver *driver);
    int tty_unregister_driver(struct tty_driver *driver);
    /* 释放tty驱动 */
    void put_tty_driver(struct tty_driver *d);
    /* tty端口销毁 */
    void tty_port_destroy(struct tty_port *port);
    /* 设置tty文件操作集 */
    void tty_set_operations(struct tty_driver *driver, const struct tty_operations *op);

    alloc_tty_driver/tty_alloc_driver完成了如下事宜
    1. 分配tty_driver数据结构
    2. 初始化tty_driver的magic、num、owner、flags成员
    3. 如果flags不包含TTY_DRIVER_DEVPTS_MEM, 分配num个tty_struct和ktermios指针变量(!!!注意只是分配了指针!!!
    4. 如果flags不包含TTY_DRIVER_DYNAMIC_ALLOC, 分配num个tty_port指针变量(!!!注意只是分配了指针!!!
    5. 分配cdev数据结构

    tty_register_driver完成了如下事宜
    1. 如果指定了主设备号, 通过register_chrdev_region静态申请设备号; 否则通过alloc_chrdev_region动态分配设备号
    2. 如果flags包含TTY_DRIVER_DYNAMIC_ALLOC, 调用tty_cdev_add分配并注册字符设备对象
    2.1 tty_cdev_add通过cdev_alloc动态分配字符设备对象并赋值给tty_driver的cdevs, 并将tty_fops赋值给cdev的ops, 最后通过cdev_add注册字符设备对象
    3. 通过list_add将tty_driver变量添加到tty_drivers链表
    4. 如果flags不包含TTY_DRIVER_DYNAMIC_DEV, 通过tty_register_device注册num个tty设备
    5. 调用proc_tty_register_driver注册/proc/tty/driver/*

    设备相关API主要如下:

    /* tty设备注册 */
    struct device *tty_register_device(struct tty_driver *driver, unsigned index, struct device *dev);
    struct device *tty_port_register_device(struct tty_port *port, struct tty_driver *driver, unsigned index, struct device *device);
    struct device *tty_port_register_device_attr(struct tty_port *port, struct tty_driver *driver, unsigned index, struct device *device, void *drvdata, const struct attribute_group **attr_grp);
    /* tty设备注销 */
    void tty_unregister_device(struct tty_driver *driver, unsigned index);

    tty_port_register_device完成了如下事宜
    1. 调用tty_port_link_device将tty_port赋值到tty_driver的ports变量
    2. 调用tty_register_device_attr注册tty设备
    2.1 根据tty_driver的type调用pty_line_name或者tty_line_name生成tty名称
    2.2 如果tty_driver的flags包含TTY_DRIVER_DYNAMIC_ALLOC, 调用tty_cdev_add分配并注册字符设备对象
    2.2.1 tty_cdev_add通过cdev_alloc动态分配字符设备对象并赋值给tty_driver的cdevs, 并将tty_fops赋值给cdev的ops, 最后通过cdev_add注册字符设备对象
    2.3 分配device数据结构, 并赋值devt、class、parent、release、groups成员; 通过dev_set_name设置device设备名称; 通过dev_set_drvdata设置device的成员变量driver_data, 对于uart驱动来说为tty_port变量
    2.4 通过device_register向系统注册设备

    4. TTY数据结构

    tty核心层包含如下几个重要数据结构

    tty_struct是tty设备的"动态抽象", 和文件句柄的功能类似, 还保存了tty设备生命周期中的临时信息, 其生命周期为从打开tty设备开始, 到关闭tty设备结束; 主要供核心层使用

    struct tty_struct {
        int    magic;
        struct kref kref;
        struct device *dev;                  /* 指向tty设备 */
        struct tty_driver *driver;           /* 指向tty驱动 */
        const struct tty_operations *ops;    /* 指向tty_driver::tty_operations */
        int index;                           /* 指向tty设备的编号(如tty0、tty1中的0、1) */
    
        struct ld_semaphore ldisc_sem;
        struct tty_ldisc *ldisc;             /* 该tty对应的线路规程 */ 
    
        struct mutex atomic_write_lock;
        struct mutex legacy_mutex;
        struct mutex throttle_mutex;
        struct rw_semaphore termios_rwsem;
        struct mutex winsize_mutex;
        spinlock_t ctrl_lock;
        spinlock_t flow_lock;
        /* Termios values are protected by the termios rwsem */
        struct ktermios termios, termios_locked;
        struct termiox *termiox;    /* May be NULL for unsupported */
        char name[64];
        struct pid *pgrp;        /* Protected by ctrl lock */
        struct pid *session;
        unsigned long flags;
        int count;
        struct winsize winsize;        /* winsize_mutex */
        unsigned long stopped:1,    /* flow_lock */
                  flow_stopped:1,
                  unused:BITS_PER_LONG - 2;
        int hw_stopped;
        unsigned long ctrl_status:8,    /* ctrl_lock */
                  packet:1,
                  unused_ctrl:BITS_PER_LONG - 9;
        unsigned int receive_room;    /* Bytes free for queue */
        int flow_change;
    
        struct tty_struct *link;
        struct fasync_struct *fasync;
        int alt_speed;        /* For magic substitution of 38400 bps */
        wait_queue_head_t write_wait;
        wait_queue_head_t read_wait;
        struct work_struct hangup_work;
        void *disc_data;              /* 指向n_tty_data, 用于保存线路规程缓冲区信息 */ 
        void *driver_data;            /* 保存tty驱动数据, 对于uart为uart_state*/ 
        spinlock_t files_lock;        /* protects tty_files list */
        struct list_head tty_files;
    
        int closing;
        unsigned char *write_buf;
        int write_cnt;
        /* If the tty has a pending do_SAK, queue it here - akpm */
        struct work_struct SAK_work;
        struct tty_port *port;               /* 指向tty端口 */
    };

    tty_driver是tty设备的主要数据结构

    struct tty_driver {
        int              magic;           /* magic number for this structure */
        struct kref      kref;            /* Reference management */
        struct cdev      **cdevs;
        struct module    *owner;
        const char       *driver_name;    /* 该tty驱动的名称, 在tty内部使用 */
        const char       *name;           /* 该tty驱动的设备名称, 体现到sysfs以及/dev/等文件系统下 */
        int              name_base;       /* offset of printed name */
        int              major;           /* 主设备号, 如TTY_MAJOR */
        int              minor_start;     /* 起始次设备号, 通常从64开始, why? */
        unsigned int     num;             /* tty驱动对应设备数 */
        short            type;            /* tty驱动类型, 如SERIAL、PTY等 */
        short            subtype;         /* tty驱动子类型*/
        struct ktermios  init_termios;    /* 初始termios, 如tty_std_termios */
        unsigned long    flags;           /* tty驱动flags */
        struct proc_dir_entry   *proc_entry;      /* /proc文件系统入口 */
        struct tty_driver       *other;           /* 只用于PTY驱动 */
    
        /*
         * 指向tty相关数据结构
         */
        struct tty_struct **ttys;
        struct tty_port **ports;
        struct ktermios **termios;
        void *driver_state;                    /* tty驱动私有数据 */ 
    
        const struct tty_operations *ops;      /* tty文件操作集 */ 
        struct list_head tty_drivers;
    };

    tty_port是tty设备固有属性的"静态抽象", 保存了该tty设备的一些固定的属性, 供具体的tty驱动使用

    struct tty_port {
        struct tty_bufhead   buf;                 /* Locked internally */
        struct tty_struct    *tty;                /* Back pointer */
        struct tty_struct    *itty;               /* internal back ptr */
        const struct tty_port_operations *ops;    /* Port operations */
        spinlock_t           lock;                /* Lock protecting tty field */
        int                  blocked_open;        /* Waiting to open */
        int                  count;               /* Usage count */
        wait_queue_head_t    open_wait;           /* Open waiters */
        wait_queue_head_t    delta_msr_wait;      /* Modem status change */
        unsigned long        flags;               /* User TTY flags ASYNC_ */
        unsigned long        iflags;              /* Internal flags TTY_PORT_ */
        unsigned char        console:1,           /* port is a console */
                             low_latency:1;       /* optional: tune for latency */
        struct mutex         mutex;               /* Locking */
        struct mutex         buf_mutex;           /* Buffer alloc lock */
        unsigned char        *xmit_buf;           /* Optional buffer */
        unsigned int         close_delay;         /* Close port delay */
        unsigned int         closing_wait;        /* Delay for output */
        int                  drain_delay;         /* Set to zero if no pure time
                                                     based drain is needed else
                                                     set to size of fifo */
        struct kref          kref;                /* Ref counter */
    };

    termios, 定义了POSIX终端接口操作对象; 在用户空间为struct termios, 内核空间为struct ktermios

    struct ktermios {
        tcflag_t c_iflag;        /* input mode flags */
        tcflag_t c_oflag;        /* output mode flags */
        tcflag_t c_cflag;        /* control mode flags */
        tcflag_t c_lflag;        /* local mode flags */
        cc_t c_line;             /* line discipline */
        cc_t c_cc[NCCS];         /* control characters */
        speed_t c_ispeed;        /* tty设备输入速率 */
        speed_t c_ospeed;        /* tty设备输出速率 */
    };

    tty_operations定义了硬件有关的操作, 由tty驱动实现, 被tty核心层调用

    struct tty_operations {
        struct tty_struct *(*lookup)(struct tty_driver *, struct file *, int);
        int  (*install)(struct tty_driver *, struct tty_struct *);
        void (*remove)(struct tty_driver *, struct tty_struct *);
        int  (*open)(struct tty_struct *, struct file *);                /* 打开*/ 
        void (*close)(struct tty_struct *, struct file *);               /* 关闭 */ 
        void (*shutdown)(struct tty_struct *);
        void (*cleanup)(struct tty_struct *);
        int  (*write)(struct tty_struct *, const unsigned char *, int);  /* 写入缓冲区并刷新到硬件 */
        int  (*put_char)(struct tty_struct *, unsigned char); /* 单字节写入缓冲区 */
        void (*flush_chars)(struct tty_struct *);             /* 刷新写缓冲区到硬件 */
        int  (*write_room)(struct tty_struct *);              /* 写缓冲区空闲大小 */
        int  (*chars_in_buffer)(struct tty_struct *);         /* 写缓冲区数据大小 */
        int  (*ioctl)(struct tty_struct *, unsigned int, unsigned long);
        long (*compat_ioctl)(struct tty_struct *, unsigned int, unsigned long);
        void (*set_termios)(struct tty_struct *, struct ktermios *);    /* termios设置函数 */
        void (*throttle)(struct tty_struct *);  /* 流控,当读缓冲区满时被调用,告知tty驱动不再接收数据 */
        void (*unthrottle)(struct tty_struct *);/* 流控,当读缓冲区空时被调用,告知tty驱动可以接收数据 */
        void (*stop)(struct tty_struct *);      /* 流控,告知tty驱动开始发送数据 */
        void (*start)(struct tty_struct *);     /* 流控,告知tty驱动停止发送数据 */
        void (*hangup)(struct tty_struct *);                    /* 挂起tty设备 */
        int  (*break_ctl)(struct tty_struct *, int state);      /* 线路规程BREAK控制函数 */
        void (*flush_buffer)(struct tty_struct *);              /* 清空写缓冲区并丢弃数据 */
        void (*set_ldisc)(struct tty_struct *);                 /* 线路规程设置函数 */
        void (*wait_until_sent)(struct tty_struct *, int);      /*flush_chars */ 
        void (*send_xchar)(struct tty_struct *, char);          /* 高优先级字符XON/XOFF发送函数 */
        int (*tiocmget)(struct tty_struct *);                   /* 获取当前tty设备线路规程设置 */
        int (*tiocmset)(struct tty_struct *, unsigned int set, unsigned int clear);
        int (*resize)(struct tty_struct *, struct winsize *ws);
        int (*set_termiox)(struct tty_struct *, struct termiox *tnew);
        int (*get_icount)(struct tty_struct *, struct serial_icounter_struct *icount);
    #ifdef CONFIG_CONSOLE_POLL
        int  (*poll_init)(struct tty_driver *, int line, char *options);
        int  (*poll_get_char)(struct tty_driver *, int line);
        void (*poll_put_char)(struct tty_driver *, int line, char ch);
    #endif
        const struct file_operations *proc_fops;               /* /proc文件系统操作集 */ 
    };

    tty_port_operations是tty端口相关操作,  主要是在打开和关闭tty设备(端口)时使用

    struct tty_port_operations {
        int  (*carrier_raised)(struct tty_port *);                 /* 端口载体检测 */ 
        void (*dtr_rts)(struct tty_port *port, int raise);         /* 控制DTR/RTS*/  
        void (*shutdown)(struct tty_port *);                       /* 端口关闭 */ 
        int  (*activate)(struct tty_port *, struct tty_struct *);  /* 端口激活 */ 
        void (*destruct)(struct tty_port *);                       /* 端口释放 */ 
    };

    5. TTY驱动编写

    tty驱动的编写主要步骤如下:

    1. 实现tty设备有关的文件操作集(tty_operations)
    2. 调用tty_alloc_driver分配一个tty驱动, 并设置driver中相关字段(包括tty_operations变量)
    3. 调用tty_register_driver注册tty驱动
    4. 如果需要动态注册tty设备, 则需调用tty_register_device或者tty_register_device_attr注册tty设备
    5. 接收到数据时, 调用tty_insert_flip_string或者tty_insert_flip_char将数据交给TTY core; TTY core需要发送数据时, 会调用driver提供的回调函数, 在那里面访问硬件送出数据即可

    参考:
    <Linux字符设备驱动>
    <Linux TTY框架子系统>
    <Chapter 18. TTY Drivers>

  • 相关阅读:
    原型模板分享——国外高阶版“大众点评”TripAdvisor
    插画手绘入门教程
    十大用户体验设计失败案例,你知道吗?
    用摹客,轻松管理设计规范!
    摹客“独门绝技”之评论审阅篇
    政务管理系统原型模板分享
    产品经理如何做需求分析?
    python基础知socket编程
    异常处理
    元类,__call__方法和单例模式
  • 原文地址:https://www.cnblogs.com/hzl6255/p/9533327.html
Copyright © 2020-2023  润新知