Linux驅動程序編寫&&應用程序對她的調用


原文: https://blog.csdn.net/u013000434/article/details/17270227

 

    Linux驅動程序的開發,我相信這是很多致力於嵌入式學習的騷年的終極夢想,不管是技術含量,還是薪金待遇,她都一一完美的體現了出來!當然,crk_13也一樣!不過,越是誘人的東西往往也越是可望而不可即,或許大家都對驅動開發的難度之大,要求之高有所耳聞!以我個人的經歷來看,編寫驅動程序確實需要你對Linux的整個體系有一個全面的認識(包括系統編程、文件操作、硬件結構),不過,要想入個門、了解驅動程序開發的大致步驟,卻也沒有傳說中的那么困難!

        首先,我們以一個最簡單的驅動程序——永遠不變的hello_world為例,了解一下linux驅動程序編寫的一般步驟與規則。

        先簡單的介紹下Linux驅動程序的基本結構:必須包含一下兩個宏

        module_init(init_func);

        module_exit(exit_func);

        並實現init_func()、exit_func()函數,在模塊被加載和移除的時候會分別被調用

        一個簡單的hello_world驅動:

[cpp]  view plain  copy
 
  1. #include <linux/kernel.h>  
  2. #include <linux/module.h>  
  3. #include <linux/init.h>  
  4.    
  5. static int __init hello_init()  
  6. {  
  7.     printk("Hello, world!\n");  
  8.     return 0;  
  9. }  
  10.    
  11. static void __exit hello_exit()  
  12. {  
  13.     printk("Goodbye, cruel world!\n");  
  14. }  
  15.    
  16. module_init(hello_init);  
  17. module_exit(hello_exit);  

        這就是一個最簡單的驅動程序,編譯之后,我們可以把生成的hello.ko文件拷到開發板上,分別執行如下命令:

        insmod hello.ko       #加載模塊,會調用hello_init()#

        rmmod hello       #移除模塊,會調用hello_exit()#

 

        下面,我將以一個S5PV210的led驅動程序為例,一步一步叫大家編寫一個屬於自己的驅動程序,並學會使用它:

        目的:將開發板上的led燈(以一個燈為例,其他的一樣)作為一個設備,為其編寫驅動程序,並調用!

        准備工作:①、硬件結構:led1(GPJ0_3)②、內核:kernel,可以在www.kernel.org下載

        一、編譯好我們自己的內核

                以我自己的kernel.tar.gz為例:

                ①、tar -xvzf kernel.tar.gz        #將內核源碼解壓到當前目錄#

                ②、cd ./kernel        #解壓完后,進入kernel根目錄#

                ③、cp ./arch/arm/configs/XXX_deconfig.config  .config        #從arch/arm/configs/目錄下拷貝一個配置文件到kernel根目錄,重命名為.config#

                ④、make zImage       #編譯生成內核鏡像文件(大約20分鍾左右)#

                OK,一切順利的話,我們就得到了一個我們自己編譯好的內核zImage

        二、有了內核之后,我們就可以開始寫我們的驅動程序了(如果上一步沒有成功,並不是說我們不能寫代碼,只是我們的驅動程序是為內核服務的,我們必須為她指定一個內核,他才能編譯過去)

                ①、首先我們需要為設備(也就是我們的led燈)分配設備號(包括這設備號、從設備號),我們一般采用動態分配法:

                        dev_t  led_device_num;

                        alloc_chrdev_region(&led_device_num, 0, 1, "led_dev");        #動態分配設備號#

                ②、有了設備號之后,我們就可以找到相應的設備,並對這個設備進行我們想要的操作了!永遠不要忘記:在Linux的眼中,一切都是文件!對於文件來說,最常用的操作不外乎:open,read,write,seek,close等!因此,我們也應該提供類似的函數對設備進行操作,供應用程序使用,而不應該自己隨便寫個函數實現功能!

                在./include/linux/fs.h文件中定義了一個結構體——file_operations,這是一個非常重要的結構體,我們編寫驅動程序的時候,無非是對這個結構體中定義的一些函數的實現。如:

                        static struct file_operations led_dev_fops =
                        {
                                .read = led_dev_read,
                                .write = led_dev_write,
                                .open = led_dev_open,
                                .owner = THIS_MODULE
                        };

                led_dev_read,led_dev_write,led_dev_open分別是對read,write,open函數指針的的初始化,以后,當應用程序對led設備(我們現在編寫的)進行open、read、write操作時,就會分別調用“=”后面的函數,當然,我們會在后面實現這幾個函數。

                可是,我們只是定義了一個結構體變量、並進行了初始化啊,設備怎么知道呢,內核又怎么把系統的函數與我們自己實現的函數聯系起來呢?所以,我們還應該把這種函數對應關系告訴我們的設備:

                        static struct cdev led_dev;        #定義一個字符型設備變量#

                        cdev_init (&led_dev, &led_dev_fops);        #初始化該字符設備,主要是對函數指針的初始化#

                ③、字符設備最終是由內核調用的,前面的工作都還只是閉門造車,現在車造好了,該上路了,是不是需要得到交通部門的許可呢?同樣,我們應該告訴內核,我們需要注冊這樣一個設備:

                        cdev_add (&led_dev, led_dev_num, 1);        #注冊該設備#

                ④、完成上述步驟后,我們的初始化工作(即:獲取設備號、注冊設備)已經做好,接着我們就要編寫我們自己的文件操作函數了,即:led_dev_open、led_dev_read、led_dev_write。

                ⑤、后續處理。做好這些之后,我們需要在卸載模塊時,釋放我們申請的設備號、並卸載該設備:

                        cdev_del (&led_dev);       #卸載設備#
                        unregister_chrdev_region(led_dev_num, 1);       #釋放設備號#

附上一段我自己編寫的代碼:

[cpp]  view plain  copy
 
  1. #include <linux/module.h>                                                         
  2. #include <linux/kernel.h>                                                         
  3. #include <linux/init.h>                                                           
  4. #include <linux/ioport.h>  
  5. #include <asm/io.h>  
  6. #include <asm/uaccess.h>  
  7.   
  8. #include <linux/io.h>                              
  9. #include <linux/gpio.h>   
  10.   
  11. #include <linux/cdev.h>  
  12. #include <linux/fs.h>  
  13.   
  14. static dev_t led_dev_num;  
  15. static struct cdev led_dev;    
  16. static int copen(struct inode *node, struct file *filep)  
  17. {  
  18.     printk ("led_dev is open!\n");          
  19.     gpio_set_value(S5PV210_GPJ0(3), 0);    //gpj0_3輸出低電平,led1亮  
  20.     return 0;  
  21. }   
  22.   
  23. static ssize_t cread (struct file *filep, char __user *buf, size_t count, loff_t *f_pos)  
  24. {          
  25.     gpio_set_value(S5PV210_GPJ0(3), 1);    //gpj0_3輸出高電平,led1滅  
  26.     printk ("led_dev is read!\n");  
  27.     return 0;  
  28. }   
  29.   
  30. static ssize_t cwrite (struct file *filep, const char __user *buf, size_t count, loff_t *f_pos)  
  31. {  
  32.     printk ("led_dev is write!\n");  
  33.     return 0;  
  34. }   
  35.   
  36. static struct file_operations led_dev_fops =  
  37. {  
  38.     .read  = cread,  
  39.     .write = cwrite,  
  40.     .open  = copen,  
  41.     .owner = THIS_MODULE  
  42. };   
  43.   
  44. static int __init led_dev_init (void)  
  45. {  
  46.     int res;  
  47.     res = alloc_chrdev_region(&led_dev_num, 0, 1, "led_dev");    //動態分配設備號  
  48.     if (res < 0)  
  49.         return res;  
  50.     
  51.     cdev_init (&led_dev, &led_dev_fops);    //設備初始化  
  52.     cdev_add (&led_dev, led_dev_num, 1);    //添加設備  
  53.    
  54.     s3c_gpio_cfgpin (S5PV210_GPJ0(3), S3C_GPIO_OUTPUT);    //將gpj0_3端口配置為輸出模式  
  55.       
  56.     printk ("led_dev is init\n");  
  57.     return 0;  
  58. }   
  59.   
  60. static void __exit led_dev_exit (void)  
  61. {  
  62.     cdev_del (&led_dev);    //卸載設配  
  63.     unregister_chrdev_region(led_dev_num, 1);    //注銷設備號  
  64.     printk ("led_dev is exit\n");  
  65. }   
  66.   
  67. module_init(led_dev_init);  
  68. module_exit(led_dev_exit);    
  69.   
  70. MODULE_LICENSE("Dual BSD/GPL");    //許可證,注意最好是遵循gpl協議,否則,在板子上加載模塊的時候,可能會不成功!  
  71. MODULE_AUTHOR("crk_13");  
  72. MODULE_DESCRIPTION("This is my first driver");  
  73. MODULE_SUPPORTED_DEVICE("none");  


         三、編譯。程序編寫好之后,我們需要編譯led_dev.c,我這里提供一個Makefile文件,做一點小小的改動,然后make就行了! 

[cpp]  view plain  copy
 
  1. # Comment/uncomment the following line to disable/enable debugging  
  2. #DEBUG = y  
  3.   
  4. # Add your debugging flag (or not) to CFLAGS  
  5. ifeq ($(DEBUG),y)  
  6.   DEBFLAGS = -O -g # "-O" is needed to expand inlines  
  7. else  
  8.   DEBFLAGS = -O2  
  9. endif  
  10.   
  11. #CFLAGS += $(DEBFLAGS) -I$(LDDINCDIR)  
  12.   
  13. ifneq ($(KERNELRELEASE),)  
  14. # call from kernel build system  
  15. # <span style="color:#000000;">把obj-m后面的目標文件名改成你自己的源文件名</span>  
  16. obj-m   := led_dev.o  
  17.   
  18. else  
  19. # <span style="color:#000000;">把路徑改成你的內核的根目錄路徑</span>  
  20. KERNELDIR ?= /home/yw/kernel  
  21. PWD       := $(shell pwd)  
  22.   
  23. default:  
  24.     $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINCDIR=$(PWD)/../include modules  
  25.   
  26. endif  
  27.   
  28.   
  29.   
  30. clean:  
  31.     rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions  
  32.   
  33. depend .depend dep:  
  34.     $(CC) $(CFLAGS) -M *.c > .depend  
  35.   
  36.   
  37. ifeq (.depend,$(wildcard .depend))  
  38. include .depend  
  39. endif  


        編譯完成之后,可以看到,在當前目錄下會生成一個led_dev.ko文件,我們可以在開發板上通過nfs掛載到宿主機,然后執行下面的一系列操作:
                ①、insmod led_dev.ko        #加載模塊#
                ②、cat /proc/devices        #查看設備led_dev的主設備號(因為是動態分配的,一開始我們並不知道)#
                ③、mknod /dev/led_dev c 250 0        #創建設備節點,設備名可以隨便起,比一定要是led_dev#
                ④、ls /dev/led_dev -l        #可以查看是否存在我們創建的節點,及主、從設備號#
                ⑤、cat /dev/led_dev        #執行該命令,相當於同時執行了open、read操作#
                ⑥、rmmod led_dev        #移除設備,在編寫了應用程序調用的實驗時,先不要執行這一步,在做完后面的執行應用程序之后,再移除,否則,又要返回①重做一遍!#

 

        四、應用程序的編寫        其實應用程序相對來說格式比較靈活,無非就是想操作普通文件一樣調用open,read。。。不多說了,看看代碼吧!

[cpp]  view plain  copy
 
  1. #include <stdio.h>  
  2. #include <sys/types.h>  
  3. #include <sys/stat.h>  
  4. #include <fcntl.h>  
  5. #include <unistd.h>  
  6. #include <stdlib.h>  
  7.   
  8. int main(int argc, const char* argv[])    //執行的時候輸入./main /dev/led_dev 10說明打開的是/dev/led_dev設備,10用於后面的延時10s  
  9. {  
  10.     char buf[20];  
  11.     int fd = open (argv[1], O_RDWR);    //調用led_dev_open函數,led1亮  
  12.   
  13.     sleep(atoi(argv[2]));    //延時10s  
  14.     read (fd, buf, sizeof(buf));    //調用led_dev_read函數,led1滅  
  15.   
  16.     close(fd);  
  17.     return 0;  
  18. }  

        應用程序編寫完成,開始編譯,並執行:
        ①、arm-linux-gcc main.c -o main       #編譯main.c,重命名為main,當然,為了方便,你完全可以寫個Makefile#
        ②、./main /dev/led_dev 10        #執行main,打開/dev/led_dev設備,延時10s#
        可以看到現象:led1先亮10s,然后熄滅!我們的led驅動就做好啦!哈哈,當然,這只是一個模板,談不上規范和實用,你完全可以把她改的更好哦!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM