linux字符設備驅動簡介及其實現方法(例程)

環境:主機-Ubuntu 16.04,開發板-友善之臂tiny4412開發板,內核版本linux-3.5

參考《Linux設備驅動開發詳解基於最新的Linux 4.0內核》(宋寶華編著)

字符設備驅動,在Linux設備驅動中較爲基礎,本文將大致分析Linux字符設備驅動的整體結構,並編寫簡單的驅動模板。

 

字符設備:在I/O傳輸過程中以字符爲單位串行順序進行傳輸的設備,即以一個字節一個字節進行讀寫操作的設備,是面向字節流的,如鼠標、鍵盤、串口、GPIO等。

 

1、字符設備驅動結構

在Linux內核中,使用struct cdev結構體來描述一個字符設備,其定義如下:

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

 

cdev結構體的dev_t成員定義了設備號,爲32位(高12位爲主設備號,低20位爲次設備號)。設備號相關宏如下:

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))    //獲取主設備號
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))     //獲取次設備號
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))         //指定主、次設備號生成設備號

分別爲獲取主設備號、獲取次設備號、生成設備號。

關於設備號,有兩個重要的函數,分別完成設備號的分配與釋放:

/* 向系統申請設備號,已知起始設備號的情況 */
int register_chrdev_region(dev_t from, unsigned count, const char *name);

/* 向系統動態申請未被佔用的設備號,不需知道設備號 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

/* 釋放所申請的設備號 */
void unregister_chrdev_region(dev_t from, unsigned count);

 

Linux內核提供了一組函數以用於操作cdev結構體:

/* 初始化cdev結構的成員,並建立cdev和file_operation之間的連接 */
void cdev_init(struct cdev *cdev, const struct file_operations *fops);

/* 動態申請一個cdev結構內存 */
struct cdev *cdev_alloc(void);

/* 向系統添加一個字符設備cdev */
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

/* 向系統刪除一個字符設備cdev */
void cdev_del(struct cdev *p);

 

2、struct file_operations結構體

cdev結構體中的成員file_operation非常重要,其定義了字符設備驅動提供給虛擬文件系統接口函數,大部分的對設備操作函數都要經過這個結構體。

定義:

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
};

 

3、字符設備驅動編寫步驟

(1)編寫linux內核模塊的加載與卸載函數;

(2)file_operation結構體賦值;

(3)註冊cdev;

(4)實現file_operation中的read()/write()等函數;

 

 

 

 

筆記:

關於加載/卸載函數的__init、__exit:

在include/init.h中有定義:(在include/linux/complier.h有 # define __section(S) __attribute__ ((__section__(#S))) )

#define __init        __section(.init.text) __cold notrace
#define __exit          __section(.exit.text) __exitused __cold notrace

“section”關鍵字會將被修飾的變量或函數編譯到指定的區域(可執行文件中的段),如.init.text、.exit.text,詳解請研究__attribute__ ((__section__())) 屬性設置

 

 

未完待續。。。

 

發佈了26 篇原創文章 · 獲贊 51 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章