本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Linux驱动实践:一步一步编写字符设备驱动程序
sz199511 · 298浏览 · 发布于2021-11-22 +关注

字符设备的驱动程序,有两套不同的API函数,并且在文中详细演示了利用旧的API函数来编写驱动程序。这篇文章,我们继续这个话题,实际演示一下:字符设备驱动程序的另一套API函数的使用方法。

这篇文章,我们继续这个话题,实际演示一下:字符设备驱动程序的另一套API函数的使用方法。

API 函数

这里主要关注下面这 3 个函数:

// 静态注册设备 
int register_chrdev_region(dev_t from, unsigned count, const char *name); 

// 动态注册设备 
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name); 

// 卸载设备 
void unregister_chrdev_region(dev_t from, unsigned count);

    关于静态和动态注册,主要的区别就在于:主设备号由谁来主导分配!

    静态注册:由我们的驱动程序来指定主设备号,即参数1:from;

    动态注册:由操作系统来分配,驱动程序提供一个变量来接收该设备号,即参数1: dev 指针;

    另外,在Linux 2.6后期的内核版本中,引入了 cdev 结构来描述一个字符设备,它的结构体成员是:

    struct cdev { 
        struct kobject kobj;    // 内嵌的kobject对象 
        struct module *owner;   // 所属模块 
        const struct file_operations *ops;//文件操作结构体 
        struct list_head list;  // 链表句柄 
        dev_t dev;              // 设备号 
        unsigned int count; 
    };

      与这个结构体相关的处理函数有:

      • void cdev_init(struct cdev *,struct file_operations *);

      • 初始化 cdev 的成员,主要是设置 file_operations。

      • strcut cdev *cdev_alloc(void);

      • 动态申请 cdev 内存。

      • void cdev_put(strcut cdev *p);

      • 与 count 计数相关的操作。

      • int cdev_add(struct cdev *,dev_t ,unsigned );

      • 向系统中添加一个 cdev,注册字符设备,需要在驱动被加载的时候调用。

      • void cdev_del(struct cdev *);

      • 从系统中删除一个 cdev,注销字符设备,需要在驱动被卸载的时候调用。

      • 后面在代码演示的时候,可以看到cdev结构是如何被使用的。

      编写驱动

      按照惯例,我们仍然按照步骤,来讨论如何利用上述的APIs,来手写一个字符设备的驱动程序。

      以下所有操作的工作目录,都是与上一篇文章相同的,即:~/tmp/linux-4.15/drivers/。

      创建驱动目录和驱动程序

      $ cd linux-4.15/drivers/ 
      $ mkdir my_driver2 
      $ cd my_driver2 
      $ touch driver2.c

        driver2.c 文件的内容如下(不需要手敲,文末有代码下载链接):

        #include <linux/module.h> 
        #include <linux/kernel.h> 
        #include <linux/ctype.h> 
        #include <linux/device.h> 
        #include <linux/cdev.h> 
        
        static struct cdev my_cdev; 
        static dev_t dev_no; 
        
        int driver2_open(struct inode *inode, struct file *file) 
        { 
            printk("driver2_open is called. \n"); 
            return 0; 
        } 
        
        ssize_t driver2_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) 
        { 
            printk("driver2_read is called. \n"); 
            return 0; 
        } 
        
        ssize_t driver2_write (struct file *file, const char __user *buf, size_t size, loff_t *ppos) 
        { 
            printk("driver2_write is called. \n"); 
            return 0; 
        } 
          
        static const struct file_operations driver2_ops={ 
            .owner = THIS_MODULE, 
            .open  = driver2_open, 
            .read  = driver2_read, 
            .write = driver2_write, 
        }; 
          
        static int __init driver2_init(void) 
        { 
            printk("driver2_init is called. \n"); 
        
            // 初始化cdev结构 
            cdev_init(&my_cdev, &driver2_ops); 
        
            // 注册字符设备 
            alloc_chrdev_region(&dev_no, 0, 2, "driver2"); 
            cdev_add(&my_cdev, dev_no, 2); 
        
            return 0; 
        } 
          
        static void __exit driver2_exit(void) 
        { 
            printk("driver2_exit is called. \n"); 
        
            // 注销设备 
            cdev_del(&my_cdev);  
        
            // 注销设备号 
            unregister_chrdev_region(dev_no, 2); 
        } 
          
        MODULE_LICENSE("GPL"); 
        module_init(driver2_init); 
        module_exit(driver2_exit);


          这里看一下加载驱动模块时调用的 driver2_init( ) 函数,其中的 cdev_init 用来把cdev结构体与 file_operations 发生关联。

          在调用 alloc_chrdev_region( ) 时,操作系统分配了主设备号,并且保存在 dev_no 变量中,然后 cdev_add() 再把设备号与cdev结构体进行关联。

          创建 Makefile 文件

          $ touch Makefile

            内容如下:

            ifneq ($(KERNELRELEASE),) 
                obj-m := driver2.o 
            else 
                KERNELDIR ?= /lib/modules/$(shell uname -r)/build 
                PWD := $(shell pwd) 
            default: 
                $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 
            clean: 
                $(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean 
            endif

              编译驱动模块

              $ make

                得到驱动程序: driver2.ko 。

                加载驱动模块

                在加载驱动模块之前,先来检查一下系统中,几个与驱动设备相关的地方。

                先看一下 /dev 目录下,目前还没有我们的设备节点( /dev/driver2 )。

                $ ll /dev/driver2 
                ls: cannot access '/dev/driver2': No such file or directory

                  再来查看一下 /proc/devices 目录下,也没有 driver2 设备的设备号。

                  $ cat /proc/devices

                    /proc/devices 文件: 列出字符和块设备的主设备号,以及分配到这些设备号的设备名称。

                    为了方便查看打印信息,把dmesg输出信息清理一下:

                    $ sudo dmesg -c

                      执行如下指令,加载驱动模块:

                      $ sudo insmod driver2.ko

                        当驱动程序被加载的时候,通过 module_init( ) 注册的函数 driver2_init() 将会被执行,那么其中的打印信息就会输出。

                        还是通过 dmesg 指令来查看驱动模块的打印信息:

                        $ dmesg

                          此时,驱动模块已经被加载了!

                          来查看一下 /proc/devices 目录下显示的设备号:

                          $ cat /proc/devices

                            设备已经注册了,主设备号是: 244 。

                            但是,此时在/dev目录下,还没有我们需要的设备节点。

                            在上一篇文章中介绍过,还可以利用 Linux 用户态的 udev 服务来自动创建设备节点。

                            现在,我们手动创建设备节点:

                            $ sudo mknod -m 660 /dev/driver2 c 244 0

                              主设备号 244 是从 /proc/devices 查到的。

                              检查一下是否创建成功:

                              $ ll /dev/driver2

                                现在,设备的驱动程序已经加载了,设备节点也被创建好了,应用程序就可以来操作(读、写)这个设备了。

                                应用程序

                                应用程序仍然放在 ~/tmp/App/ 目录下。

                                $ mkdir ~/tmp/App/app_driver2 
                                $ cd ~/tmp/App/app_driver2 
                                $ touch app_driver2.c

                                  文件内容如下:

                                  #include <stdio.h> 
                                  #include <unistd.h> 
                                  #include <fcntl.h> 
                                  
                                  
                                  int main(void) 
                                  { 
                                      int ret; 
                                      int read_data[4] = { 0 }; 
                                      int write_data[4] = {1, 2, 3, 4}; 
                                      int fd = open("/dev/driver2", O_RDWR); 
                                      if (-1 != fd) 
                                      { 
                                          ret = read(fd, read_data, 4); 
                                          printf("read ret = %d \n", ret); 
                                  
                                          ret = write(fd, write_data, 4); 
                                          printf("write ret = %d \n", ret); 
                                      } 
                                      else 
                                      { 
                                          printf("open /dev/driver2 failed! \n"); 
                                      } 
                                  
                                      return 0; 
                                  }


                                    接下来就是编译和测试了:

                                    $ gcc app_driver2.c -o app_driver2 
                                    $  
                                    $ sudo ./app_driver2  
                                    [sudo] password for xxx: <输入用户密码> 
                                    read ret = 0  
                                    write ret = 0

                                      从返回值来看,成功打开了设备,并且调用读函数、写函数都成功了!

                                      继续用dmesg命令查看一下:

                                      卸载驱动模块

                                      卸载指令:

                                      $ sudo rmmod driver2

                                        此时,/proc/devices 下主设备号 244 的 driver2 已经不存在了。

                                        再来看一下 dmesg的打印信息:

                                        可以看到:驱动程序中的 driver2_exit( ) 被调用执行了!

                                        小结

                                        以上就是利用“新的” API 函数,来编写字符设备的驱动程序。

                                        代码结构还是非常清晰的,这得益于Linux良好的驱动程序架构设计!这也是每一名架构师需要学习、努力模仿的地方。


                                        相关推荐

                                        将Fedora 29升级到Fedora 30

                                        吴振华 · 704浏览 · 2019-05-14 22:00:02
                                        使用Nginx反向代理到go-fastdfs

                                        iamitnan · 726浏览 · 2019-05-23 13:42:00
                                        利用VLC搭建组播流服务器

                                        追忆似水年华 · 2693浏览 · 2019-06-14 11:27:06
                                        用Bash脚本监控Linux上的内存使用情况

                                        吴振华 · 974浏览 · 2019-06-24 11:27:02
                                        加载中

                                        0评论

                                        评论
                                        不积跬步无以至千里,不积小流无以成江海!
                                        分类专栏
                                        小鸟云服务器
                                        扫码进入手机网页