這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)如何從點一個燈開始學(xué)寫Linux字符設(shè)備驅(qū)動,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
平?jīng)鰏sl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18980820575(備注:SSL證書合作)期待與您的合作!
這里介紹一下如何利用Linux驅(qū)動模型來完成一個LED燈設(shè)備驅(qū)動。點一個燈有什么好談呢?況且Linux下有專門的leds驅(qū)動子系統(tǒng)。
在很多嵌入式系統(tǒng)里,有可能需要實現(xiàn)數(shù)字開關(guān)量輸出,比如:
LED狀態(tài)顯示
閥門/繼電器控制
蜂鳴器
......
嵌入式Linux一般需求千變?nèi)f化,也不可能這些需求都有現(xiàn)成設(shè)備驅(qū)動代碼可供使用,所以如何學(xué)會完成一個開關(guān)量輸出設(shè)備的驅(qū)動,一方面點個燈可以比較快了解如何具體寫一個字符類設(shè)備驅(qū)動,另一方面實際項目中對于開關(guān)量輸出設(shè)備就可以這樣干,所以是具有較強的實用價值的。
要完成這樣一個開關(guān)量輸出GPIO的驅(qū)動程序,需要梳理梳理下面這些概念:
設(shè)備編號
設(shè)備掛載
關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
字符設(shè)備是通過文件系統(tǒng)內(nèi)的設(shè)備名稱進行訪問的,其本質(zhì)是設(shè)備文件系統(tǒng)樹的節(jié)點。故Linux下設(shè)備也是一個文件,Linux下字符設(shè)備在/dev目錄下。可以在開發(fā)板的控制臺或者編譯的主Linux系統(tǒng)中利用ls -l /dev查看,如下圖:
對于ls -l列出的屬性,做一個比較細的解析:
細心的朋友或許會發(fā)現(xiàn)設(shè)備號屬性,在有的文件夾下列出來不是這樣,這就對了!普通文件夾下是這樣:
差別在于一個是文件大小,一個是設(shè)備號。
再細心一點的朋友或許還會問,這些/dev下的文件時間屬性為神馬都相差無幾?這是因為/dev設(shè)備樹節(jié)點是在內(nèi)核啟動掛載設(shè)備驅(qū)動動態(tài)生成的,所以時間就是系統(tǒng)開機后按次序生成的,你如不信,不妨重啟一下系統(tǒng)在查看一下。
常見文件類型:
d: directory 文件夾
l: link 符號鏈接
p: FIFO pipe 管道文件,可以用mkfifo命令生成創(chuàng)建
s: socket 套接字文件
c: char 字符型設(shè)備文件
b: block 塊設(shè)備文件
-:常規(guī)文件
回到設(shè)備號,設(shè)備號是一個32位無符號整型數(shù),其中:
12位用來表示主設(shè)備號,用于標(biāo)識設(shè)備對應(yīng)的驅(qū)動程序。
20位用來表示次設(shè)備號,用于正確確定設(shè)備文件所指的設(shè)備。
這怎么理解呢,看下串口類設(shè)備就比較清楚了:
主設(shè)備號一樣證明這些設(shè)備共用了一個驅(qū)動程序,而次設(shè)備號不一樣,則對應(yīng)了不同的串口設(shè)備。那么怎么得到設(shè)備號呢?
/*下列定義位于./include/linux/types.h */typedef u32 __kernel_dev_t;typedef __kernel_dev_t dev_t;/* 下面宏用于生成主設(shè)備號,次設(shè)備號 *//* 下列定義位于./include/linux/Kdev_t.h */#define MINORBITS 20#define MINORMASK ((1U << MINORBITS) - 1)#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
使用舉例:
/* 主設(shè)備號 */MAJOR(dev_t dev); /* 次設(shè)備號 */MINOR(dev_t dev);
為簡化問題,本文描述一下動態(tài)加載設(shè)備驅(qū)動模塊,暫不考慮設(shè)備樹。參考<
#!/bin/sh#-----------------------------------------------------------------------module="led" device="led" mode="664" group="staff"# 利用insmod命令加載設(shè)備模塊insmod -f $module.ko $* || exit 1# 獲取系統(tǒng)分配的主設(shè)備號 major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"`# 刪除舊節(jié)點rm -f /dev/${device} #創(chuàng)建設(shè)備文件節(jié)點mknod /dev/${device} c $major 0#設(shè)置設(shè)備文件節(jié)點屬性chgrp $group /dev/${device} chmod $mode /dev/${device}
這里要提一下/proc/devices,這是一個文件記錄了字符和塊設(shè)備的主設(shè)備號,以及分配到這些設(shè)備號的設(shè)備名稱。比如使用cat命令來列出這個文件內(nèi)容:
字符設(shè)備由什么關(guān)鍵數(shù)據(jù)結(jié)構(gòu)進行抽象的呢,來看看:
file_operations定義在./include/linux/fs.h
cdev定義在./include/linux/cdev.h
cdev中與字符設(shè)備驅(qū)動編程相關(guān)兩個數(shù)據(jù)域:
const struct file_operations *ops;
dev_t dev;設(shè)備編號
文件操作符是一個龐大的數(shù)據(jù)結(jié)構(gòu),常規(guī)字符設(shè)備驅(qū)動一般需要實現(xiàn)下面一些函數(shù)指針:
read:用來實現(xiàn)從設(shè)備中讀取數(shù)據(jù)
write:用于實現(xiàn)寫入數(shù)據(jù)到設(shè)備
ioctl:實現(xiàn)執(zhí)行設(shè)備特定命令的方法
open:用實現(xiàn)打開一個設(shè)備文件
release:當(dāng)file結(jié)構(gòu)被釋放時,將調(diào)用這個接口函數(shù)
先上代碼(可左右滑動顯示):
#include#include #include #include /* printk() */#include #include #include /* everything... */#include #include /* copy_*_user *//*這里具體參考不同開發(fā)板的電路 GPIOC24 */#define LED_CTRL (2*32+24) static const unsigned int led_pad_cfg = LED_CTRL;struct t_led_dev{ struct cdev cdev; unsigned char value; };struct t_led_dev led_dev;static dev_t led_major;static dev_t led_minor=0; static int led_open(struct inode * inode,struct file * filp){ filp->private_data = &led_dev; printk ("led is opened!\n"); return 0; }static int led_release(struct inode * inode, struct file * filp){ return 0; }static ssize_t led_read(struct file * file, char __user * buf, size_t count, loff_t *ppos){ ssize_t ret=1; if(copy_to_user(&(led_dev.value),buf,1)) return -EFAULT; printk ("led is read!\n"); return ret; }static ssize_t led_write(struct file * filp, const char __user *buf, size_t count,loff_t *ppos){ unsigned char value; ssize_t retval = 0; if(copy_from_user(&value,buf,1)) return -EFAULT; if(value&0x01) gpio_set_value(led_pad_cfg, 1); else gpio_set_value(led_pad_cfg, 0); printk ("led is written!\n"); return retval; }static const struct file_operations led_fops = { .owner = THIS_MODULE, .read = led_read, .write = led_write, .open = led_open, .release = led_release, };static void led_setup_cdev(struct t_led_dev * dev, int index){ /* 初始化字符設(shè)備驅(qū)動數(shù)據(jù)域 */ int err,devno = MKDEV(led_major,led_minor+index); cdev_init(&(dev->cdev),&led_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &led_fops; /* 字符設(shè)備注冊 */ err = cdev_add(&(dev->cdev),devno,1); if(err) printk(KERN_NOTICE "Error %d adding led %d",err,index); }static int led_gpio_init(void){ if (gpio_request(LED_CTRL, "led") < 0) { printk("Led request gpio failed\n"); return -1; } printk("Led gpio requested ok\n"); gpio_direction_output(LED_CTRL, 1); gpio_set_value(LED_CTRL, 1); return 0; }/* 注銷設(shè)備 */void led_cleanup(void){ dev_t devno = MKDEV(led_major, led_minor); gpio_set_value(LED_CTRL, 0); gpio_free(LED_CTRL); cdev_del(&led_dev.cdev); unregister_chrdev_region(devno, 1); //注銷設(shè)備號 }/* 注冊設(shè)備 */static int led_init(void){ int result; dev_t dev = MKDEV( led_major, 0 ); /* 動態(tài)分配設(shè)備號 */ result = alloc_chrdev_region(&dev, 0, 1, "led"); if(result<0) return result; led_major = MAJOR(dev); memset(&led_dev,0,sizeof(struct t_led_dev)); led_setup_cdev(&led_dev,0); led_gpio_init(); printk ("led device initialised!\n"); return result; } module_init(led_init); module_exit(led_cleanup); MODULE_DESCRIPTION("Led device demo"); MODULE_AUTHOR("embinn"); MODULE_LICENSE("GPL");
來總結(jié)一下要點:
init函數(shù),需要用module_init宏包起來,本例中即為led_init,module_init宏的作用就是選編譯為模塊或進內(nèi)核的底層實現(xiàn),建議剛開始不必深究。一般而言主要實現(xiàn):
申請分配主設(shè)備號alloc_chrdev_region
為特定設(shè)備相關(guān)數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存
將入口函數(shù)(open read write等)與字符設(shè)備驅(qū)動的cdev抽象數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián)
將主設(shè)備與驅(qū)動程序cdev相關(guān)聯(lián)
申請硬件資源,初始化硬件
調(diào)用cdev_add注冊設(shè)備
exit函數(shù),一樣需要用module_exit包起來,主要負責(zé):
釋放硬件資源
調(diào)用cdev_del刪除設(shè)備
調(diào)用unregister_chrdev_region注銷設(shè)備號
用戶空間與驅(qū)動數(shù)據(jù)交換
copy_to_user,如其名一樣,將內(nèi)核空間數(shù)據(jù)信息傳遞到用戶空間
copy_from_user,如其名一樣,從用戶空間拷貝數(shù)據(jù)進內(nèi)核空間
善用printk進行驅(qū)動調(diào)試,這是內(nèi)核打印函數(shù)。
gpio相關(guān)操作函數(shù),這里就不一一列舉其作用了,比較容易理解。
#include#include #include #include #define READ_SIZE 10int main(int argc, char **argv){ int fd,count; float value; unsigned char buf[READ_SIZE+1]; printf( "Cmd argv[0]:%s,argv[1]:%s,argv[2]:%s\n",argv[0],argv[1],argv[2] ); if( argc<2 ){ printf( "[Usage: test device_name ]\n" ); exit(0); } if(strlen(argv[2]!=1) printf( "Invalid parameter\n" ); if(( fd = open(argv[1],O_WRONLY ))<0){ printf( "Error:can not open the device: %s\n",argv[1] ); exit(1); } if(argv[2][0] == '1') buf[0] = 1; else if(argv[2][0] == '0') buf[0] = 0; else printf( "Invalid parameter\n" ); printf("write: %d\n",buf[0]); if( (count = write( fd, buf ,1 ))<0 ){ perror("write error.\n"); exit(1); } close(fd); printf("close device %s\n",argv[1] ); return 0; }
編譯成可執(zhí)行文件,調(diào)用前面的腳本加載設(shè)備后,在/dev下就可以看到led設(shè)備了。比如測試代碼編譯成ledTest執(zhí)行文件,則使用下面命令運行測試程序就可以看到led控制效果了:
/*打開led 具體取決電路是高有效還是低有效*/./ledTest /dev/led 1./ledTest /dev/led 0
這樣就實現(xiàn)了用戶空間驅(qū)動底層設(shè)備了,實際應(yīng)用代碼就可以這樣去訪問底層的字符型設(shè)備。
最后總結(jié)了簡單字符設(shè)備的驅(qū)動開發(fā)的一些要點,以及如何動態(tài)加載,在設(shè)備文件系統(tǒng)樹上創(chuàng)建設(shè)備節(jié)點,并演示了驅(qū)動以及驅(qū)動使用的基本要點。
上述就是小編為大家分享的如何從點一個燈開始學(xué)寫Linux字符設(shè)備驅(qū)動了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。