字符设备驱动程序——点亮、熄灭LED操作
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
字符設(shè)備:是指只能一個(gè)字節(jié)一個(gè)字節(jié)讀寫的設(shè)備,不能隨機(jī)讀取設(shè)備內(nèi)存中的某一數(shù)據(jù),讀取數(shù)據(jù)需要按照先后數(shù)據(jù)。字符設(shè)備是面向流的設(shè)備,常見的字符設(shè)備有鼠標(biāo)、鍵盤、串口、控制臺和LED設(shè)備等。
每一個(gè)字符設(shè)備都在/dev目錄下對應(yīng)一個(gè)設(shè)備文件。linux用戶程序通過設(shè)備文件(或稱設(shè)備節(jié)點(diǎn))來使用驅(qū)動(dòng)程序操作字符設(shè)備。
Linux操作系統(tǒng)將所有的設(shè)備看成文件,以操作文件的方式訪問設(shè)備。應(yīng)用程序不能直接操作硬件,而是使用統(tǒng)一的接口函數(shù)調(diào)用硬件的驅(qū)動(dòng)程序。這組接口被稱為系統(tǒng)調(diào)用,在庫函數(shù)中定義??梢栽趃libc的fcntl.h、unistd.h、sys/ioctl.h等文件中看到如下的定義,這個(gè)些文件也可以在交叉編譯工具鏈的/usr/local/arm/3.4.1/include目錄下找到。
extern int open (__const char *__file, int __oflag, ...) __nonnull ((1));
extern ssize_t read (int __fd, void *__buf, size_t __nbytes);
extern ssize_t write (int __fd, __const void *__buf, size_t __n);
extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;
.......
對于上述每個(gè)系統(tǒng)調(diào)用,驅(qū)動(dòng)程序中都有一個(gè)與之對應(yīng)的函數(shù)。對于字符設(shè)備驅(qū)動(dòng)程序,這些函數(shù)集合在一個(gè)file_operations類型的數(shù)據(jù)結(jié)構(gòu)中。file_operations結(jié)構(gòu)在Linux內(nèi)核的include/linux/fs.h文件中定義。
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 *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
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 *, struct dentry *, 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 (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
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 (*dir_notify)(struct file *filp, unsigned long arg);
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);
};
當(dāng)應(yīng)用程序使用open函數(shù)打開某個(gè)設(shè)備時(shí),設(shè)備驅(qū)動(dòng)程序的file_operations結(jié)構(gòu)中的open成員就會(huì)被調(diào)用;當(dāng)應(yīng)用程序使用read、write、ioctl等函數(shù)讀寫、控制設(shè)備時(shí),驅(qū)動(dòng)程序的file_operations結(jié)構(gòu)中的相應(yīng)成員(read、write、ioctl等)就會(huì)被調(diào)用。從這個(gè)角度來說,編寫字符設(shè)備驅(qū)動(dòng)程序就是為具體硬件的file_operations結(jié)構(gòu)編寫各個(gè)函數(shù)(并不需要全部實(shí)現(xiàn)file_operations結(jié)構(gòu)中的成員)。
那么,當(dāng)應(yīng)用程序通過open、read、write等系統(tǒng)調(diào)用訪問某個(gè)設(shè)備文件時(shí),Linux系統(tǒng)怎么知道去調(diào)用那個(gè)驅(qū)動(dòng)程序的file_operations結(jié)構(gòu)中的open、read、write等成員呢?
(1)、設(shè)備文件主/次設(shè)備號
設(shè)備文件分為字符設(shè)備、塊設(shè)備,比如PC機(jī)上的串口屬于字符設(shè)備,硬盤屬于塊設(shè)備。在PC機(jī)上運(yùn)行命令“l(fā)s /dev/ttyS0 /dev/hdal -l”可以看到:
brw-rw---- 1 root disk ??3, 1 ?5月 21 08:29 /dev/hdal
crw-rw---- 1 root uucp ?4, 64 ?5月 21 08:29 /dev/ttyS0
“brw-rw----”中的“b”表示/dev/hdal是個(gè)塊設(shè)備,他的主設(shè)備號是3,次設(shè)備號是1;“crw-rw----”中的“c”表示/dev/ttyS0是個(gè)塊設(shè)備,它的主設(shè)備號位4,次設(shè)備號為64。
(2)、模塊初始化時(shí),將主設(shè)備號與file_operations結(jié)構(gòu)一起向內(nèi)核注冊
驅(qū)動(dòng)程序有一個(gè)初始化函數(shù),在安裝驅(qū)動(dòng)程序時(shí)會(huì)調(diào)用它。在初始化函數(shù)中,會(huì)將驅(qū)動(dòng)程序的file_operations結(jié)構(gòu)連同主設(shè)備號一起向內(nèi)核進(jìn)行注冊。對于字符設(shè)備使用如下以下函數(shù)進(jìn)行注冊:
extern int register_chrdev(unsigned int, const char *,const struct file_operations *);
這樣,應(yīng)用程序操作設(shè)備文件時(shí),Linux系統(tǒng)就將會(huì)根據(jù)設(shè)備文件的類型(是字符設(shè)備還是塊設(shè)備)、主設(shè)備號找到在內(nèi)核中注冊的file_operations結(jié)構(gòu)(對于塊設(shè)備位block_device_operations結(jié)構(gòu)),次設(shè)備號供驅(qū)動(dòng)程序自身用來分辨它是同類設(shè)備中的第幾個(gè)。
編寫字符驅(qū)動(dòng)程序的過程大概如下。
(1)、編寫驅(qū)動(dòng)程序初始化函數(shù)
進(jìn)行必要的初始化,包括硬件初始化、向內(nèi)核注冊驅(qū)動(dòng)程序等。
(2)、構(gòu)造file_operations結(jié)構(gòu)中要用到的各個(gè)成員函數(shù)
實(shí)際的驅(qū)動(dòng)程序當(dāng)然比上述兩個(gè)步驟復(fù)雜,但這兩個(gè)步驟已經(jīng)可以讓我們編寫比較簡單的驅(qū)動(dòng)程序,比如LED控制。其他比較高級的技術(shù),比如中斷、select機(jī)制、fasync異步通知機(jī)制,將在其他章節(jié)的例子中介紹。
LED驅(qū)動(dòng)程序源碼分析:
本節(jié)以一個(gè)簡單的LED驅(qū)動(dòng)程序作為例子,讓讀者初步了解驅(qū)動(dòng)程序的開發(fā)。
本例子的開發(fā)板使用引腳GPF4~6外接3個(gè)LED,它們的操作方法以前已經(jīng)做了詳細(xì)的說明。
(1)、引腳功能為輸出;
(2)、要點(diǎn)亮LED,令引腳輸出0;
(3)、要熄滅LED,令引腳輸出1。
硬件連接方式如下圖:
1、LED驅(qū)動(dòng)程序代碼分析
下面按照函數(shù)調(diào)用的順序進(jìn)行講解。
模塊的初始化函數(shù)和卸載函數(shù)如下:
/*
* 執(zhí)行“insmod first_drv.ko”命令時(shí)就會(huì)調(diào)用這個(gè)函數(shù)
*/
static int __init first_drv_init(void)
{
/* 注冊字符設(shè)備驅(qū)動(dòng)程序
?* 參數(shù)為主設(shè)備號、設(shè)備名字、file_operations結(jié)構(gòu);本例子中寫0,
?* 內(nèi)核就會(huì)自動(dòng)分配一個(gè)主設(shè)備號,使用major變量存儲。
?* 這樣,主設(shè)備號和具體的file_operations結(jié)構(gòu)就聯(lián)系起來了
?* 操作主設(shè)備號為major的設(shè)備文件時(shí),就會(huì)調(diào)用first_drv_fops中的相關(guān)成員函數(shù)
*/
99 : major = register_chrdev( 0, "first_drv", &first_drv_fops);
?
/* 創(chuàng)建一個(gè)struct class的指針
?* 第一個(gè)參數(shù)默認(rèn)“THIS_MODULE”
?* 第二個(gè)參數(shù)和register_chrdev()函數(shù)中的驅(qū)動(dòng)設(shè)備名字相同
*/
firstdrv_class = class_create(THIS_MODULE, "first_drv");
/* 創(chuàng)建/dev/xxx設(shè)備
?* 第一個(gè)參數(shù)為上面創(chuàng)建的struct class類型的變量
?* 第二個(gè)參數(shù)為NULL,第三個(gè)參數(shù)為MKDEV(major, 0)
?* 第四個(gè)參數(shù)為NULL,第五個(gè)參數(shù)為設(shè)備名字
*/
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xxx");
?
/* 將物理端口進(jìn)行映射
*/
GPFCON = (volatile unsigned long *)ioremap(0x56000050, 16);
GPFDATA = GPFCON+1;
return 0;
}
?
/* 執(zhí)行rmmod first_drv命令時(shí)就會(huì)調(diào)用這個(gè)函數(shù)
?*
*/
static void __exit first_drv_exit(void)
{
/* 卸載驅(qū)動(dòng)程序 */
unregister_chrdev(major, "first_drv");
/* 卸載/dev/目錄下的設(shè)備 */
class_device_unregister(firstdrv_class_dev);
/* 銷毀class_create()創(chuàng)建的類 */
class_destroy(firstdrv_class);
/* 取消端口映射 */
iounmap(GPFCON);
}
/* 這兩行指定驅(qū)動(dòng)程序的初始化和卸載函數(shù) */
119 : module_init(first_drv_init);
120 : module_exit(first_drv_exit);
/* 驅(qū)動(dòng)模塊的許可證聲明 */
MODULE_LICENSE("GPL");
第119、120兩行用來指明裝載、卸載模塊時(shí)所調(diào)用的函數(shù)。也可以不使用這兩行,但是需要將這兩個(gè)函數(shù)的名字改為init_module、cleanup_module。
執(zhí)行“insmod first_drv.ko”命令時(shí)就會(huì)調(diào)用first_drv_init函數(shù),這個(gè)函數(shù)核心的代碼只有第99行。它調(diào)用register_chrdev函數(shù)向內(nèi)核注冊驅(qū)動(dòng)程序;內(nèi)核會(huì)自動(dòng)分配主設(shè)備號,并保存在major變量中,將主設(shè)備號位major與file_operations結(jié)構(gòu)first_drv_fops聯(lián)系起來。以后應(yīng)用程序操作主設(shè)備號位major的設(shè)備文件時(shí),比如open、read、write、ioctl,first_drv_fops中的相應(yīng)成員函數(shù)就會(huì)被調(diào)用。但是,first_drv_fops中并不需要全部實(shí)現(xiàn)這些函數(shù),用到哪個(gè)就實(shí)現(xiàn)哪個(gè)。
執(zhí)行“rmmod first_drv”命令時(shí)就會(huì)調(diào)用first_drv_exit函數(shù),它進(jìn)而調(diào)用unregister_chrdev函數(shù)卸載驅(qū)動(dòng)程序,它的功能與register_chrdev函數(shù)相反。
first_drv_init、first_drv_exit函數(shù)前的“__init”、“__exit”只有在將驅(qū)動(dòng)程序靜態(tài)鏈接進(jìn)內(nèi)核時(shí)才有意義。前者表示first_drv_init函數(shù)的代碼被放在“.init.text”段中,這個(gè)段在使用一次后被釋放(這可以節(jié)省內(nèi)存);后者表示first_drv_exit函數(shù)的代碼段被放在“.exit.data”段中,在連接內(nèi)核時(shí)這個(gè)段沒有使用,因?yàn)椴荒苄遁d靜態(tài)鏈接的驅(qū)動(dòng)程序。
下面看看first_drv_fops結(jié)構(gòu)的組成。
/* 這個(gè)結(jié)構(gòu)是字符設(shè)備驅(qū)動(dòng)程序的核心
?* 當(dāng)應(yīng)用程序操作設(shè)備文件時(shí)所調(diào)用的open、read、write等函數(shù)
?* 最終會(huì)調(diào)用這個(gè)結(jié)構(gòu)中的對應(yīng)函數(shù)
*/
static struct file_operations first_drv_fops = {
???81 .owner = THIS_MODULE,/* 這是一個(gè)宏,指向編譯模塊時(shí)自動(dòng)創(chuàng)建的__this_module變量 */
????.open = ?first_drv_open, ????
????.write = first_drv_write, ???
};
第81行的宏THIS_MODULE在include/linux/module.h中定義如下,__this_module變量在編譯模塊時(shí)自動(dòng)創(chuàng)建,無需關(guān)注這點(diǎn)。
# define THIS_MODULE (&__this_module)
file_operations類型的first_drv_fops結(jié)構(gòu)是驅(qū)動(dòng)中最重要的數(shù)據(jù)結(jié)構(gòu),編寫字符設(shè)備驅(qū)動(dòng)程序的主要工作也是填充其中的各個(gè)成員。比如本驅(qū)動(dòng)程序中用到open、write成員被設(shè)為first_drv_open、first_drv_write函數(shù),前者用來初始化LED所用的GPIO引腳,后者用來根據(jù)用戶傳入的參數(shù)設(shè)置GPIO的輸出電平。
first_drv_open函數(shù)的代碼如下:
/* 應(yīng)用程序?qū)υO(shè)備文件/dev/xxx執(zhí)行open()時(shí),
* 就會(huì)調(diào)用first_drv_open 函數(shù)
*/
static int first_drv_open(struct inode *inode, struct file *file)
{
/* 設(shè)置GPIO引腳的功能,本驅(qū)動(dòng)中LED所涉及的GPIO引腳設(shè)為輸出功能 */
/*配置GPF4、5、6為輸出*/
*GPFCON ?&= ?~( (0X3<<(4*2)) | (0X3<<(5*2)) | (0X3<<(6*2))); //首先清零
*GPFCON ?|= ?( (0X1<<(4*2)) | (0X1<<(5*2)) | (0X1<<(6*2))); ?//后置1
return 0;
}
在應(yīng)用程序執(zhí)行“open(“/dev/xxx”)”系統(tǒng)調(diào)用時(shí),first_drv_open函數(shù)將被調(diào)用。它用來將LED所涉及的GPIO引腳設(shè)為輸出功能。不在模塊的初始化函數(shù)中進(jìn)行這些設(shè)置的原因是:雖然加載了模塊,但是這個(gè)模塊卻不一定會(huì)被用到,就是說這些引腳不一定用于這些用途,它們可能在其他模塊中另作他用。所以,在使用時(shí)才去設(shè)置它,我們把對引腳的初始化放在open操作中。
first_drv_write函數(shù)的代碼如下:
/* 應(yīng)用程序?qū)υO(shè)備文件/dev/xxx執(zhí)行write()函數(shù)時(shí)
* 就會(huì)調(diào)用first_drv_write函數(shù)
*/
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
/* 拷貝從應(yīng)用程序傳遞過來的值到val變量中 */
copy_from_user(&val, buf, count);
if(val == 1){
//點(diǎn)燈
*GPFDATA &= ~((0X1<<(4)) | (0X1<<(5)) | (0X1<<(6)));
}else{
//滅燈
*GPFDATA |= ((0X1<<(4)) | (0X1<<(5)) | (0X1<<(6)));
}
return 0;
}
應(yīng)用程序執(zhí)行系統(tǒng)調(diào)用write()時(shí),first_drv_write函數(shù)將被調(diào)用,copy_from_user(&val, buf, count);作用是將用戶傳遞的輸出取出,根據(jù)用戶傳遞的值來設(shè)置LED燈的亮滅。
注意:應(yīng)用程序執(zhí)行的open、write等系統(tǒng)調(diào)用,它們的參數(shù)和驅(qū)動(dòng)程序中相應(yīng)函數(shù)的參數(shù)不是一一對應(yīng)的,其中經(jīng)過了內(nèi)核文件系統(tǒng)層的轉(zhuǎn)換。
系統(tǒng)調(diào)用的原型如下:
int open(const char *pathname, int flags);
ssize_t write(int fd, const void *buf, size_t count);
int ioctl(int fd, int request, ...);
ssize_t read(int fd, void *buf, size_t count);
file_operations結(jié)構(gòu)中的成員如下:
int (*open) (struct inode *, struct file *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
可以看到,這些參數(shù)有很大一部分非常相似。
(1)、系統(tǒng)調(diào)用open傳入的參數(shù)已經(jīng)被內(nèi)核文件系統(tǒng)層處理了,在驅(qū)動(dòng)程序中看不出原來的參數(shù)了。
(2)、系統(tǒng)調(diào)用ioctl的參數(shù)個(gè)數(shù)可變,一般最多傳入3個(gè):后面兩個(gè)參數(shù)與file_operations結(jié)構(gòu)中ioctl成員的后兩個(gè)參數(shù)對應(yīng)。
(3)、系統(tǒng)調(diào)用read傳入的buf、count參數(shù),對應(yīng)file_operations結(jié)構(gòu)中read成員的buf、count參數(shù)。而參數(shù)offp表示用戶在文件中進(jìn)行存取操作的位置,當(dāng)執(zhí)行完讀寫操作后由驅(qū)動(dòng)程序調(diào)用。
(4)、系統(tǒng)調(diào)用write與file_operations結(jié)構(gòu)中write成員的參數(shù)關(guān)系,與第(3)點(diǎn)相似。
驅(qū)動(dòng)程序的最后,有如下描述信息,它們不是必須的。
/* 描述驅(qū)動(dòng)程序的一些信息,不是必須的 */
MODULE_AUTHOR(“http:\\www.100ask.net”); //驅(qū)動(dòng)程序作者
MODULE_DESCRIPTION(“S3C2410 LED Driver”); //一些描述信息
MODULE_LICENSE(“GPL”); //遵循的協(xié)議
驅(qū)動(dòng)程序的編譯:
在次文件的同一目錄下,制作Makefile文件,文件內(nèi)容如下:
KERN_DIR = /work/linux/linux-2.6.22.6 ?#對應(yīng)內(nèi)核的所在目錄
all:
make -C $(KERN_DIR) M=`pwd` modules
clean: ???#執(zhí)行make clean時(shí)刪除生成的文件
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += first_drv.o ?#執(zhí)行make命令生成的模塊名字為“first_drv.o”
驅(qū)動(dòng)程序測試:
首先要編譯測試驅(qū)動(dòng)程序firstdrv_test.c,它的代碼很簡單,關(guān)鍵部分如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/*
* firstdrvtest
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xxx", O_RDWR); //打開設(shè)備
if (fd < 0)
{
printf("can't open!\n");
}
if(argc != 2){
printf("Usage:\n");
printf("%s <on | off>\n",argv[0]);
return 0;
}
if(strcmp(argv[1], "on") == 0){ ?//判斷用戶運(yùn)行程序時(shí),
????????????????//添加的參數(shù)時(shí)on還是off
val = 1; ?//寫入1,既點(diǎn)亮LED
}else{
val = 0; ?//寫入0,既熄滅LED
}
write(fd, &val, 4); //寫入數(shù)據(jù)
return 0;
}
其中的open、write函數(shù)最終會(huì)調(diào)用驅(qū)動(dòng)程序中的first_drv_open、first_drv_write函數(shù)。
現(xiàn)在就可以參照firstdrv_test.c的使用說明,(直接運(yùn)行firstdrv_test命令即可看到)操作LED了,以下兩條命令點(diǎn)亮、熄滅LED。
./firstdrv_test on
./firstdrv_test off
?
轉(zhuǎn)載于:https://my.oschina.net/cht2000/blog/906223
新人創(chuàng)作打卡挑戰(zhàn)賽發(fā)博客就能抽獎(jiǎng)!定制產(chǎn)品紅包拿不停!總結(jié)
以上是生活随笔為你收集整理的字符设备驱动程序——点亮、熄灭LED操作的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用正则表达式小心换行和回车
- 下一篇: VINS(五)非线性优化与在线标定调整