Linux-驅動開發
- ■ Linux-應用程序對驅動程序的調用流程
- ■ Linux-file_operations 結構體
- ■ Linux-驅動模塊的加載和卸載
- ■ 1. 驅動編譯進 Linux 內核中
- ■ 2. 驅動編譯成模塊(Linux 下模塊擴展名為.ko)
- ■ Linux-
- ■ Linux-
- ■ Linux-設備號
- ■ Linux-設備號-分配
- ■ 靜態分配設備號
- ■ 動態分配設備號
- ■ Linux-驅動分類
- ■ 字符設備:
- ■ 字符設備-注冊與注銷函數
- ■ 字符設備-具體操作函數
- ■ 字符設備-LICENSE 和作者信息
- ■ 示例一:
- ■
- ■
- ■
■ Linux-應用程序對驅動程序的調用流程
■ Linux-file_operations 結構體
在 Linux 內核文件 include/linux/fs.h 中有個叫做 file_operations 的結構體,此結構體就是 Linux 內核驅動操作函數集合,
1588 struct file_operations {
1589 struct module *owner; // 擁有該結構體的模塊的指針,一般設置為 THIS_MODULE
1590 loff_t (*llseek) (struct file *, loff_t, int); //llseek 函數用于修改文件當前的讀寫位置。
1591 ssize_t (*read) (struct file *, char __user *, size_t, loff_t*); // read 函數用于讀取設備文件。
1592 ssize_t (*write) (struct file *, const char __user *, size_t,loff_t *); //write 函數用于向設備文件寫入(發送)數據。
1593 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1594 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1595 int (*iterate) (struct file *, struct dir_context *);
1596 unsigned int (*poll) (struct file *, struct poll_table_struct*); //是個輪詢函數,用于查詢設備是否可以進行非阻塞的讀寫。
1597 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //unlocked_ioctl 函數提供對于設備的控制功能,與應用程序中的 ioctl 函數對應。
1598 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //compat_ioctl 函數與 unlocked_ioctl 函數功能一樣,區別在于在 64 位系統上,
32 位的應用程序調用將會使用此函數。在 32 位的系統上運行 32 位的應用程序調用的是
unlocked_ioctl。
1599 int (*mmap) (struct file *, struct vm_area_struct *); //mmap 函數用于將設備的內存映射到進程空間中(也就是用戶空間),一般幀緩
沖設備會使用此函數,比如 LCD 驅動的顯存,將幀緩沖(LCD 顯存)映射到用戶空間中以后應用
程序就可以直接操作顯存了,這樣就不用在用戶空間和內核空間之間來回復制。
1600 int (*mremap)(struct file *, struct vm_area_struct *);
1601 int (*open) (struct inode *, struct file *); //open 函數用于打開設備文件。
1602 int (*flush) (struct file *, fl_owner_t id);
1603 int (*release) (struct inode *, struct file *); //release 函數用于釋放(關閉)設備文件,與應用程序中的 close 函數對應。
1604 int (*fsync) (struct file *, loff_t, loff_t, int datasync); //fasync 函數用于刷新待處理的數據,用于將緩沖區中的數據刷新到磁盤中
1605 int (*aio_fsync) (struct kiocb *, int datasync); //aio_fsync 函數與 fasync 函數的功能類似,只是 aio_fsync 是異步刷新待處理的
數據。
1606 int (*fasync) (int, struct file *, int);
1607 int (*lock) (struct file *, int, struct file_lock *);
1608 ssize_t (*sendpage) (struct file *, struct page *, int, size_t,loff_t *, int);
1609 unsigned long (*get_unmapped_area)(struct file *, unsigned long,unsigned long, unsigned long, unsigned long);
1610 int (*check_flags)(int);
1611 int (*flock) (struct file *, int, struct file_lock *);
1612 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,loff_t *, size_t, unsigned int);
1613 ssize_t (*splice_read)(struct file *, loff_t *, structpipe_inode_info *, size_t, unsigned int);
1614 int (*setlease)(struct file *, long, struct file_lock **, void**);
1615 long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
1617 void (*show_fdinfo)(struct seq_file *m, struct file *f);
1618 #ifndef CONFIG_MMU
1619 unsigned (*mmap_capabilities)(struct file *);
1620 #endif
1621 };
序號 | 描述 |
---|---|
1 | 驅動加載成功以后會在“/dev”目錄下生成一個相應的文件 |
2 | 比如現在有個叫做/dev/led 的驅動文件,此文件是 led 燈的驅動文件。應用程序使用 open 函數來打開文件/dev/led,使用完成以后使用close 函數關閉/dev/led 這 個文件。 open和 close 就是打開和關閉 led 驅動的函數,如果要點亮或關閉 led |
3 | 應用程序運行在用戶空間,而 Linux 驅動屬于內核的一部分,因此驅動運行于內核空間 |
4 | |
5 | |
6 | |
7 | |
8 |
■ Linux-驅動模塊的加載和卸載
■ 1. 驅動編譯進 Linux 內核中
這樣當 Linux 內核啟動的時候就會自動運行驅動程序。
■ 2. 驅動編譯成模塊(Linux 下模塊擴展名為.ko)
在Linux 內核啟動以后使用“insmod”命令加載驅動模塊。在調試驅動的時候一般都選擇將其編譯為模塊,這樣我們修改驅動以后只需要編譯一下驅動代碼即可,不需要編譯整個 Linux 代碼。
module_init(xxx_init); //注冊模塊加載函數 當使用“insmod”命令加載驅動的時候, xxx_init 這個函數就會被調用
module_exit(xxx_exit); //注冊模塊卸載函數 當使用“rmmod”命令卸載具體驅動的時候, xxx_exit 函數就會被調用。
示例一:
第15行,調用函數module_init 來聲明 xxx_init 為驅動入口函數,當加載驅動的時候 xxx_init函數就會被調用。
第16行,調用函數module_exit來聲明xxx_exit為驅動出口函數,當卸載驅動的時候xxx_exit函數就會被調用。
關鍵字功能 | 作用 | 說明 |
---|---|---|
加載驅動 | insmod | insmod 是最簡單的模塊加載命令 例如 insmod drv.ko , insmod 命令不能解決模塊的依賴關系, 比如 drv.ko 依賴 first.ko 這個模塊,就必須先使用insmod 命令加載 first.ko 這個模塊,然后再加載 drv.ko 這個模塊。 |
加載驅動 | modprobe | modprobe 會分析模塊的依賴關系,然后會將所有的依賴模塊都加載到內核中, 因此modprobe 命令相比 insmod 要智能一些。 推薦使用 modprobe 命令來加載驅動。 modprobe 命令默認會去/lib/modules/目錄中查找模塊, |
卸載驅動 | modprobe -r | 例如 modprobe -r drv.ko 使用 modprobe 命令可以卸載掉驅動模塊所依賴的其他模塊,前提是這些依賴模塊已經沒有被其他模塊所使用,否則就不能使用 modprobe 來卸載驅動模塊。 |
卸載驅動 | rmmod | 例如 rmmod drv.ko 推薦使用 rmmod 命令。 |
■ Linux-
■ Linux-
■ Linux-設備號
Linux 中每個設備都有一個設備號,設備號由主設備號和次設備號兩部分組成,
主設備號表示某一個具體的驅動,
次設備號表示使用這個驅動的各個設備。
dev_t 其實就是 unsigned int 類型,是一個 32 位的數據類型。高 12 位為主設備號, 低 20 位為次設備號。
typedef unsigned int __u32;
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t; dev_t 其實就是 unsigned int 類型,是一個 32 位的數據類型。高 12 位為主設備號, 低 20 位為次設備號。
在include/linux/kdev_t.h 中提供了幾個關于設備號的操作函數(本質是宏),如下所示:
#define MINORBITS 20 宏 MINORBITS 表示次設備號位數,一共是 20 位。
#define MINORMASK ((1U << MINORBITS) - 1) 宏 MINORMASK 表示次設備號掩碼。
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) 宏 MAJOR 用于從 dev_t 中獲取主設備號,將 dev_t 右移 20 位即可。
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) 宏 MINOR 用于從 dev_t 中獲取次設備號,取 dev_t 的低 20 位的值即可。
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) 宏 MKDEV 用于將給定的主設備號和次設備號的值組合成 dev_t 類型的設備號。
■ Linux-設備號-分配
■ 靜態分配設備號
具體分配的內容可以查看文檔 Documentation/devices.txt。
看硬件平臺運行過程中有沒有使用這個主設備號,使用“cat /proc/devices” 命令即可查看當前系統中所有已經使用了的設備號。
■ 動態分配設備號
靜態分配設備號很容易帶來沖突問題, Linux 社區推薦使用動態分配設備號,在注冊字符設備之前先申請一個設備號,系統會自動給你一個沒有被使用的設備號,這樣就避免了沖突。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) //用于申請設備號,dev:保存申請到的設備號。baseminor: 次設備號起始地址, alloc_chrdev_region 可以申請一段連續的多個設備號,這些設備號的主設備號一樣,但是次設備號不同,次設備號以 baseminor 為起始地址地址開始遞增。一般 baseminor 為 0,也就是說次設備號從 0 開始。count: 要申請的設備號數量。name:設備名字。void unregister_chrdev_region(dev_t from, unsigned count) //設備號釋放函數from:要釋放的設備號。count: 表示從 from 開始,要釋放的設備號數量。
■ Linux-驅動分類
■ 字符設備:
字符設備就是一個一個字節,按照字節流進行讀寫操作的設備,讀寫數據是分先后順序的。比如我們最常見的點燈、按鍵、 IIC、 SPI,LCD 等等都是字符設備,這些設備的驅動就叫做字符設備驅動。
■ 字符設備-注冊與注銷函數
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)major: 主設備號,
name:設備名字,指向一串字符串。
fops: 結構體 file_operations 類型指針,指向設備的操作函數集合變量。
■ 字符設備-具體操作函數
打開
關閉
讀
寫 操作
■ 字符設備-LICENSE 和作者信息
MODULE_LICENSE() //添加模塊 LICENSE 信息
MODULE_AUTHOR() //添加模塊作者信息
■ 示例一:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>#define CHRDEVBASE_MAJOR 200 //主設備號
#define CHRDEVBASE_NAME "chrdevbase" //名字static char readbuf[100]; /*讀緩沖 */
static char writebuf[100]; /* 寫緩沖 */
static char kerneldata[] = {"kernel data!"};static int chrdevbase_open(struct inode *inode, struct file *filp)
{// printk("chrdevbase_open\r\n");return 0;
}static int chrdevbase_release(struct inode *inode, struct file *filp)
{// printk("chrdevbase_release\r\n");return 0;
}static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count,loff_t *ppos)
{ int ret = 0;//printk("chrdevbase_read\r\n");memcpy(readbuf, kerneldata, sizeof(kerneldata));ret = copy_to_user(buf, readbuf, count);if(ret == 0) {} else {}return 0;
}static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{int ret = 0;//printk("chrdevbase_write\r\n");ret = copy_from_user(writebuf, buf, count);if(ret == 0) {printk("kernel recevdata:%s\r\n", writebuf);} else {}return 0;
}/** 字符設備 操作集合*/
static struct file_operations chrdevbase_fops={.owner = THIS_MODULE,.open = chrdevbase_open,.release = chrdevbase_release,.read = chrdevbase_read,.write = chrdevbase_write,
};static int __init chrdevbase_init(void)
{int ret = 0;printk("chrdevbase_init\r\n");/* 注冊字符設備 */ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(ret < 0) {printk("chrdevbase init failed!\r\n");}return 0;
}static void __exit chrdevbase_exit(void)
{printk("chrdevbase_exit\r\n"); /* 注銷字符設備 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
}/*模塊入口與出口
*/
module_init(chrdevbase_init); /* 入口 */
module_exit(chrdevbase_exit); /* 出口 */MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");