linux 字符设备驱动学习。

  1. 编写实际的驱动函数类似于 led_open, led_write 等。
  2. 定义一个 file_operations 结构体,把 led_open 等实际的驱动函数填充进去。
  3. 调用 register_chrdev 函数,把 file_operations 结构体注册到内核里面去。
  4. 调用 register_chrdev 的这个函数,一般是 led_init ,也叫做驱动的入口函数。
  5. 使用 module_init(led_init); 这个宏来修饰 led_init 这个入口函数。内核会寻找 module_init 这个结构体,找到之后就会调用里面的函数指针 led_init 来调用驱动的入口函数了。
  6. 驱动注册之后,/dev/ 下面的设备一般是字符设备,就是属性是 c 开头的,还有相应的主设备号 major,和次设备号 minor。
  7. 在内核的 vfs 里面,有一个数组,这个数组每一位指向一个主设备号,register_chrdev 就是把 file_operations 挂接到这个数组的主设备号上面。内核需要调用相应的驱动函数,也是按照主设备号去这个数组里面去找相应的 file_operations 来进行操作。
  8. 出口函数 led_exit 里面调用 卸载函数 unregister_chrdev,然后用 module_exit 这个宏来修饰。
  9. 复制其他驱动下面的 makefile 到当前驱动的目录,简单的只需要修改 obj-m += 这一项为当前的驱动 即可。然后 make 即可生成驱动。
  10. 使用 cat /proc/devices 来查看内核目前支持的设备。
  11. 在 tq2440 里面编写的代码如下:

    1 #include <linux/module.h>
    2 #include <linux/kernel.h>
    3 #include <linux/fs.h>
    4 #include <linux/init.h>
    5 #include <linux/delay.h>
    6 #include <asm/uaccess.h>
    7 #include <asm/irq.h>
    8 //#include <asm.io.h>
    9 //#include <asm/arch/regs-gpio.h>
    10 //#include <asm/hardware.h>
    11 
    12 
    13 static int first_drv_open(struct inode *inode, struct file *file)
    14 {
    15         printk("first_drv_open\n");
    16         return 0;
    17 }
    18 
    19 static ssize_t first_drv_write(struct file *file, const char __user *buf, si    ze_t count, loff_t * ppos)
    20 {
    21         printk("first_drv_write\n");
    22         return 0;
    23 }
    24 
    25 static struct file_operations first_drv_fops = {
    26         .owner = THIS_MODULE,
    27         .open = first_drv_open,
    28         .write = first_drv_write,
    29 };
    30 
    31 int first_drv_init(void)
    32 {
    33         register_chrdev(111, "first_drv", &first_drv_fops);
    34         return 0;
    35 }
    36 
    37 void first_drv_exit(void)
    38 {
    39         unregister_chrdev(111, "first_drv");
    40         return;
    41 }
    42 
    43 module_init(first_drv_init);
    44 module_exit(first_drv_exit);

    makefile

    1 KERN_DIR = /home/book/embed_sky/linux-2.6.30.4
    2 CC = arm-linux-gcc
    3 
    4 all:
    5         make -C $(KERN_DIR) M=`pwd` modules
    6 
    7 clean:
    8         make -C $(KERN_DIR) M=`pwd` modules clean
    9         rm -rf modules.order
    10 
    11 obj-m += first_drv.o
  12. make 编译出 ko 文件,然后复制到 nfs_root 目录。 切换到开发板上,使用 insmod xxx.ko 命令,加载驱动, 然后 cat /proc/devices,可以看到已经加载上驱动设备了。

  13. 编写驱动测试程序:

    1 #include <sys/types.h>
    2 #include <sys/stat.h>
    3 #include <fcntl.h>
    4 #include <stdio.h>
    5 
    6 int main(int argc, char **argv)
    7 {
    8         int fd;
    9         int val = 1;
    10         fd = open("/dev/xxx", O_RDWR);
    11         if (fd <0)
    12                 printf("can't open!\n");
    13         write(fd, &val, 4);
    14         return 0;
    15 }

    然后 arm-linux-gcc -o first_test first_test.c 再把生成的文件复制到 nfs 里面去。

  14. 在开发板上执行 first_test, 提示 can't open! 因为没有 /dev/xxx 这个设备。 所以我们先创建这个设备: mknod /dev/xxx c 111 0 然后再次执行,就可以了。

  15. 在驱动程序里面可以不指定主设备号,让系统自动分配,这样程序就改为:

    31 int major;
    32 
    33 int first_drv_init(void)
    34 {
    35         major = register_chrdev(0, "first_drv", &first_drv_fops);
    36         return 0;
    37 }
    38 
    39 void first_drv_exit(void)
    40 {
    41         unregister_chrdev(major, "first_drv");
    42         return;
    43 }

    然后重新编译并复制到 nfs 里面。 在开发板上,使用 rmmod first_drv 来卸载模块,然后 lsmod 确定模块卸载了, 然后使用 inmod 重新加载模块, cat /proc/devices 来看看分配到什么设备号了。 然后 rm /dev/xxx 删除原来的设备, mknod /dev/xxx c 252 0 按照新的设备号重新建立设备,这样就测试程序又可以正常工作了。

  16. 如果想要自动创建节点,那么就需要 udev,或者简化后的 mdev 来实现,mdev 根据 /sys/devices 目录下面的信息,自动创建节点。

标签: driver

添加新评论