Linux驱动学习笔记(四)
终端控制台相关概念
在Linux中,TTY(终端)是一类字符设备的统称,包括三类:控制台、串口、伪终端。
控制台是一个虚拟的终端,必须映射到真正的终端上;可以简单的理解为printk输出的地方;只输出不输入,中能在内核中访问。
伪终端由主从两个成对的设备构成。输入到主设备的数据成为从设备的输出,输入到从设备的数据成为主设备的输出,形成双向管道。
Linux内核中使用uart_driver描述串口驱动,它包含串口设备的驱动名、设备名、设备号等等:
struct uart_driver{
srtuct module*pwner;
const char*driver_name;//驱动名
const char*dev_name;//设备名
int major;//主设备号
int minor;//起始次设备号
int nr;//设备数
struct console*cons;
structuart_state *State;
structtty_driver *tty_driver;
};
串口设备驱动注册:
int uart_register_driver(struct uart_driver *drv);
端口描述结构体:
uart_port用于描述一个UART端口的地址、FIFO大小、端口类型等信息;
struct_port{
spinlock_tlock;//端口锁
unsigned intiobase;//IO端口基地址
unsigned char __iomem *membase;//IO内存基地址
unsigned int irq;//中断号
unsigned char fifosize;//传输fifo大小
const struct uart_ops *ops;
};
uart_ops操作函数集合:
unsigned int (*tx_empty)(structuart_port *);
void (*set_mctrl)(struct uart_port *, unsignedint mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, structktermios *new,struct ktermios *old);
void (*set_ldisc)(struct uart_port *, int new);
void (*pm)(struct uart_port *, unsigned intstate,unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsignedint state);
/*
* Return a string describing the type of theport
*/
const char*(*type)(struct uart_port *);
/*
* Release IO and memory resources used by theport.
* This includes iounmap if necessary.
*/
void (*release_port)(struct uart_port *);
/*
* Request IO and memory resources used by theport.
* This includes iomapping the port ifnecessary.
*/
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, structserial_struct *);
int (*ioctl)(struct uart_port *, unsigned int,unsigned long);
。。。。。。。。。。。。
};
添加端口:
int uart_add_one_port(struct uart_driver *reg, structuart_port *port);
串口驱动实现流程:
- 定义uart_driver的变量,并初始化
- 使用uart_register_driver来注册驱动
- 初始化uart_port和uart_ops函数集合
- 调用uart_add_one_port添加初始化好的uart_port
块设备驱动
块设备将数据存储在固定大小的块中,每个块的大小一般为512字节到32768字节之间。
块设备和字符设备的区别:
最大的区别是读写数据的基本单元不同,块设备读写数据的基本单元是块,而字符设备的基本单元是字节。块设备可以随机访问,而字符设备只能顺序访问。
Mapping Layer:
首先确定文件系统的block size,然后计算所请求的数据包含多少个block。调用具体文件系统的函数来访问文件的inode,确定所请求的数据在磁盘上的逻辑块地址。
Generic Block Layer:
Linux内核为块设备抽象了统一的模型,把块设备看做是由若干个扇区组成的数据空间。上层的读写请求在通用的块层被构造成一个或多个bio结构。
I/O Scheduler Layer:
I/O调度层负责将I/O操作进行排序。
块设备描述结构体:
struct gendisk {
int major; /* major number of driver */
intfirst_minor;//次设备号
intminors; /* maximum number of minors,=1 for* disks that can't be partitioned. */
chardisk_name[DISK_NAME_LEN]; /* name ofmajor driver */
const structblock_device_operations *fops;//块设备操作函数集合
structrequest_queue *queue;//请求队列
..........................................
int node_id;
};
注册块设备:
void add_disk(struct gendisk *disk);
I/O请求:
使用struct request;来表示等待处理的块设备I/0请求。
struct request{
structlist_head queuelist;//链表结构
sector_tsector;//要操作的首个扇区
unsigned longnr_sectors;//要操作的扇区数目
struct bio*bio;//请求bio结构体的链表
struct bio*biotail;//请求的bio结构体的链表尾
};
请求队列:
IO请求request所形成的队列。Linux中struct request_queue描述。
初始化请求队列,一般在块设备驱动的模块加载函数中调用。
struct request_queue *blk_init_queue(request_fn_proc*rfn,spinlock_t *lock);
清除请求队列,这函数完成将请求队列返回给系统的任务,一般在快设备驱动模块卸载函数中调用。
void blk_cleanup_queue(request_queue_t *q);
得到下一个请求:
struct request *elv_next_request(request_queue_t *queue);
从队列中删除一个请求:
void blkdev_dequeue_request(struct request *req);
分配请求队列,对于FLASH、RAM盘等完全随机访问的非机械设备,并不需要进行复杂的I/O调度,这个时候,应该使用下述函数分配1个请求队列
request_queue_t *blk_alloc_queue(int gfp_mask);
绑定“请求队列”和“制造请求”函数。
void blk_queue_make_request(request_queue_t*q,make_request_fn *mfn)
LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)判断内核版本是否小于2.6.24