Linux驱动程序编写&&应用程序对她的调用

     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 在CODE上查看代码片 派生到我的代码片
  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);  
  18.      这就是一个最简单的驱动程序,编译之后,我们可以把生成的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);       #释放设备号#

    附上一段我自己编写的代码:


    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 在CODE上查看代码片 派生到我的代码片
    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 在CODE上查看代码片 派生到我的代码片
    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驱动就做好啦!哈哈,当然,这只是一个模板,谈不上规范和实用,你完全可以把她改的更好哦!

  • 转自:http://blog.csdn.net/u013000434/article/details/17270227


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值