• linux 驱动学习(一)简单的字符设备驱动程序


      linux 系统将设备分为三种类型:字符设备、块设备和网络接口设备。

      文章将先给出字符设备驱动程序,会贴出字符设备驱动程序的测试程序并记录测试过程。参照程序记录知识点,可能会不全,以后会慢慢加 。

    注释版

     1 #include "linux/kernel.h" //内核头文件,含有一些内核常用函数的原形定义
     2 #include "linux/module.h" //包含大量加载模块需要的函数和符号的定义
     3 #include "linux/fs.h"     //包含了文件操作相关struct的定义,例如大名鼎鼎的struct file_operations
     4 #include "linux/init.h"   //包含了模块的初始化宏定义,及一些其他函数的初始化函数
     5 #include "linux/types.h"  //对一些特殊类型的定义,例如dev_t, off_t, pid_t.其实这些类型大部分都是unsigned int型通过一连串的typedef变过来的,只是为了方便阅读。
     6 #include "linux/errno.h"  //包含了对返回值的宏定义,这样用户程序可以用perror输出错误信息。
     7 #include "linux/uaccess.h"  //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
     8 #include "linux/kdev_t.h"   //知识点一 包含MAJOR(dev)、MINOR(dev) 、MKDEV(ma,mi)、print_dev_t(buffer, dev)、format_dev_t(buffer, dev)函数
     9 #define MAX_SIZE 1024
    10 
    11 static int my_open(struct inode *inode, struct file *file);//设备打开函数
    12 static int my_release(struct inode *inode, struct file *file);//设备释放
    13 static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f);//copy_to_user 从设备中获取数据
    14 static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f);//copy_from_user 写数据到设备中
    15 
    16 static char message[MAX_SIZE] = "-------congratulations--------!";//设备中的字符串
    17 static int device_num = 0;//设备号
    18 static int counter = 0;//计数用(设备调用次数)
    19 static int mutex = 0;//互斥用
    20 static char* devName = "myDevice";//设备名
    21 
    22 struct file_operations pStruct =
    23 { open:my_open, release:my_release, read:my_read, write:my_write, };//file_operations结构体,知识点二
    24 
    25 /* 注册模块 */
    26 int init_module()
    27 {
    28     int ret;
    29     /* 函数中第一个参数是告诉系统,新注册的设备的主设备号由系统分配,
    30      * 第二个参数是新设备注册时的设备名字,
    31      * 第三个参数是指向file_operations的指针,
    32      * 当用设备号为0创建时,系统一个可以用的设备号创建模块 */
    33     ret = register_chrdev(0, devName, &pStruct);
    34     if (ret < 0)
    35     {
    36         printk("regist failure!
    ");
    37         return -1;
    38     }
    39     else
    40     {
    41         printk("the device has been registered!
    ");
    42         device_num = ret;
    43         printk("<1>the virtual device's major number %d.
    ", device_num);
    44         printk("<1>Or you can see it by using
    ");
    45         printk("<1>------more /proc/devices-------
    ");
    46         printk("<1>To talk to the driver,create a dev file with
    ");
    47         printk("<1>------'mknod /dev/myDevice c %d 0'-------
    ", device_num);
    48         printk("<1>Use "rmmode" to remove the module
    ");
    49 
    50         return 0;
    51     }
    52 }
    53 /* 注销模块,函数名很特殊 */
    54 void cleanup_module()
    55 {
    56     unregister_chrdev(device_num, devName);
    57     printk("unregister it success!
    ");
    58 }
    59 
    60 static int my_open(struct inode *inode, struct file *file)
    61 {
    62         if(mutex)
    63                 return -EBUSY;
    64         mutex = 1;//上锁
    65     printk("<1>main  device : %d
    ", MAJOR(inode->i_rdev));
    66     printk("<1>slave device : %d
    ", MINOR(inode->i_rdev));
    67     printk("<1>%d times to call the device
    ", ++counter);
    68     try_module_get(THIS_MODULE);
    69     return 0;
    70 }
    71 /* 每次使用完后会release */
    72 static int my_release(struct inode *inode, struct file *file)
    73 {
    74     printk("Device released!
    ");
    75     module_put(THIS_MODULE);
    76         mutex = 0;//开锁
    77     return 0;
    78 }
    79 
    80 static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f)
    81 {
    82     if(copy_to_user(user,message,sizeof(message)))
    83     {
    84         return -EFAULT;
    85     }
    86     return sizeof(message);
    87 }
    88 
    89 static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f)
    90 {
    91     if(copy_from_user(message,user,sizeof(message)))
    92     {
    93         return -EFAULT;
    94     }
    95     return sizeof(message);
    96 }
    View Code

    纯净版

     1 #include "linux/kernel.h"
     2 #include "linux/module.h"
     3 #include "linux/fs.h"
     4 #include "linux/init.h"
     5 #include "linux/types.h"
     6 #include "linux/errno.h"
     7 #include "linux/uaccess.h"
     8 #include "linux/kdev_t.h"
     9 #define MAX_SIZE 1024
    10 
    11 static int my_open(struct inode *inode, struct file *file);
    12 static int my_release(struct inode *inode, struct file *file);
    13 static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f);
    14 static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f);
    15 
    16 static char message[MAX_SIZE] = "-------congratulations--------!";
    17 static int device_num = 0;//设备号
    18 static int counter = 0;//计数用
    19 static int mutex = 0;//互斥用
    20 static char* devName = "myDevice";//设备名
    21 
    22 struct file_operations pStruct =
    23 { open:my_open, release:my_release, read:my_read, write:my_write, };
    24 
    25 /* 注册模块 */
    26 int init_module()
    27 {
    28     int ret;
    29     /* 函数中第一个参数是告诉系统,新注册的设备的主设备号由系统分配,
    30      * 第二个参数是新设备注册时的设备名字,
    31      * 第三个参数是指向file_operations的指针,
    32      * 当用设备号为0创建时,系统一个可以用的设备号创建模块 */
    33     ret = register_chrdev(0, devName, &pStruct);
    34     if (ret < 0)
    35     {
    36         printk("regist failure!
    ");
    37         return -1;
    38     }
    39     else
    40     {
    41         printk("the device has been registered!
    ");
    42         device_num = ret;
    43         printk("<1>the virtual device's major number %d.
    ", device_num);
    44         printk("<1>Or you can see it by using
    ");
    45         printk("<1>------more /proc/devices-------
    ");
    46         printk("<1>To talk to the driver,create a dev file with
    ");
    47         printk("<1>------'mknod /dev/myDevice c %d 0'-------
    ", device_num);
    48         printk("<1>Use "rmmode" to remove the module
    ");
    49 
    50         return 0;
    51     }
    52 }
    53 /* 注销模块,函数名很特殊 */
    54 void cleanup_module()
    55 {
    56     unregister_chrdev(device_num, devName);
    57     printk("unregister it success!
    ");
    58 }
    59 
    60 static int my_open(struct inode *inode, struct file *file)
    61 {
    62         if(mutex)
    63                 return -EBUSY;
    64         mutex = 1;//上锁
    65     printk("<1>main  device : %d
    ", MAJOR(inode->i_rdev));
    66     printk("<1>slave device : %d
    ", MINOR(inode->i_rdev));
    67     printk("<1>%d times to call the device
    ", ++counter);
    68     try_module_get(THIS_MODULE);
    69     return 0;
    70 }
    71 /* 每次使用完后会release */
    72 static int my_release(struct inode *inode, struct file *file)
    73 {
    74     printk("Device released!
    ");
    75     module_put(THIS_MODULE);
    76         mutex = 0;//开锁
    77     return 0;
    78 }
    79 
    80 static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f)
    81 {
    82     if(copy_to_user(user,message,sizeof(message)))
    83     {
    84         return -EFAULT;
    85     }
    86     return sizeof(message);
    87 }
    88 
    89 static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f)
    90 {
    91     if(copy_from_user(message,user,sizeof(message)))
    92     {
    93         return -EFAULT;
    94     }
    95     return sizeof(message);
    96 }
    View Code

    字符驱动程序Makefile文件

     1 # If KERNELRELEASE is defined, we've been invoked from the
     2 # kernel build system and can use its language.
     3 ifeq ($(KERNELRELEASE),)
     4     # Assume the source tree is where the running kernel was built
     5     # You should set KERNELDIR in the environment if it's elsewhere
     6     KERNELDIR ?= /lib/modules/$(shell uname -r)/build
     7     # The current directory is passed to sub-makes as argument
     8     PWD := $(shell pwd)
     9 modules:
    10     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    11 modules_install:
    12     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
    13 clean:
    14     rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
    15 .PHONY: modules modules_install clean
    16 else
    17     # called from kernel build system: just declare what our modules are
    18     obj-m := devDrv.o
    19 endif
    View Code

     在linux终断输入make后生成devDriver.ko文件。

     在linux终断输入sudo insmod devDriver.ko  装载模块

    在linux终断输入 dmesg  查看模块信息

    在linux终断输入 lsmod  查看模块列表

    在linux终断输入

    cat /proc/devices
    Character devices:
    ...
    245 devDriver
    ...
    看到字符设备下出现了devDriver,这是函数add_cdev成功将devDriver加入到了系统中,系统给devDriver分配的主设备号是245。
    现在可以确认scull驱动程序已经运行在系统中了,那么怎么操作它呢?
    我们首先要建立一个文件节点,使用mknod 命令,mknod 用法是这样的,mknod name type major minor,分别接节点名,类型,b表示块设备,c表示字符设备(scull是字符设备,因此用c),major是主设备号(scull主设备号是249),minor为次设备号。
    sudo mknod /dev/devDriver0 c 245 0
    ls -l /dev
    可以看到,有一个叫devDriver0的设备出现在dev目录下。现在可以对这个字符设备进行读写操作了
     
    测试代码
     1 #include <sys/types.h>
     2 #include <sys/stat.h>
     3 #include <stdlib.h>
     4 #include <string.h>
     5 #include <stdio.h>
     6 #include <fcntl.h>
     7 #include <unistd.h>
     8 #define MAX_SIZE 1024
     9 
    10 int main(void)
    11 {
    12     int fd;
    13     char buf[MAX_SIZE];
    14     char get[MAX_SIZE];
    15     char devName[20], dir[50] = "/dev/";
    16     system("ls /dev/");
    17     printf("Please input the device's name you wanna to use :");
    18     gets(devName);
    19     strcat(dir, devName);
    20     fd = open(dir, O_RDWR | O_NONBLOCK);
    21     if (fd != -1)
    22     {
    23         read(fd, buf, sizeof(buf));
    24         printf("The device was inited with a string : %s
    ", buf);
    25          /* 测试写 */
    26         printf("Please input a string  :
    ");
    27         gets(get);
    28         write(fd, get, sizeof(get));
    29         /* 测试读 */
    30         read(fd, buf, sizeof(buf)); 
    31         system("dmesg");
    32         printf("
    The string in the device now is : %s
    ", buf);
    33         close(fd);
    34         return 0;
    35     }
    36     else
    37     {
    38         printf("Device open failed
    ");
    39         return -1;
    40     }
    41 }
    View Code

    在linux终断输入 sudo  gcc -o text text.c   编译测试代码

    在linux终断输入 text ,按照测试提示步骤完成测试。

    测试完成后 在linux终断输入 rmmod 卸载驱动。


    知识点一 :linux/kdev_t.h 包含的函数

    #define MINORBITS       20 //次设备号的占位数目
    #define MINORMASK       ((1U << MINORBITS) - 1)//低20位的掩码,相当于0xfffff
    
    #define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS)) //得到主设备号
    #define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))  //得到次设备号
    #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))       //将主,次设备号重新“合成”为一个数,返回
    
    #define print_dev_t(buffer, dev)                    
        sprintf((buffer), "%u:%u
    ", MAJOR(dev), MINOR(dev))    //打印主次设备号
    
    #define format_dev_t(buffer, dev)                       
        ({                                   //把主,次设备号写入到buffer指向的内存中
            sprintf(buffer, "%u:%u", MAJOR(dev), MINOR(dev));       
            buffer;                         
        })
    View Code


    知识点二:file_operations结构体详细分析

    整体结构如下:

     1 struct file_operations {
     2         struct module *owner;    //防止模块还在被使用的时候被卸载
     3         loff_t        (*llseek) ();
     4         ssize_t       (*read) ();
     5         ssize_t       (*write) ();
     6         ssize_t       (*aio_read) ();
     7         ssize_t       (*aio_write) ();
     8         int           (*readdir) ();
     9         unsigned int (*poll) ();
    10         int           (*ioctl) ();
    11         long          (*unlocked_ioctl) ();
    12         long          (*compat_ioctl) ();
    13         int           (*mmap) ();
    14         int           (*open) ();
    15         int           (*flush) ();
    16         int           (*release) ();
    17         int           (*fsync) ();
    18         int           (*aio_fsync) ();
    19         int           (*fasync) ();
    20         int           (*lock) ();
    21         ssize_t       (*sendfile) ();
    22         ssize_t       (*sendpage) ();
    23         unsigned long (*get_unmapped_area) ();
    24         int           (*check_flags) ();
    25         int           (*dir_notify) ();
    26         int           (*flock) ();
    27         ssize_t       (*splice_write) ();
    28         ssize_t       (*splice_read) ();
    29 };
    View Code

    file_operations结构体作用:Linux使用file_operations结构访问驱动程序的函数,这个结构的每一个成员的名字都对应着一个调用。用户进程利用在对设备文件进行诸如read/write操作的时候,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。

    file_operations结构体常用函数

    一般情况下,进行设备驱动程序的设计只是比较注重下面的几个方法:
    struct file_operations ***_ops={
    .owner = THIS_MODULE,
    .llseek = ***_llseek,
    .read = ***_read,
    .write = ***_write,
    .ioctl = ***_ioctl,
    .open = ***_open,
    .release = ***_release,
    };

    file_operations结构体常用函数解释

    owner:不是一个操作; 它是一个指向拥有这个结构的模块的指针.这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为
    THIS_MODULE, 一个在  中定义的宏.这个宏比较复杂,在进行简单学习操作的时候,一般初始化为THIS_MODULE。

    loff_t (*llseek) (struct file * filp , loff_t p, int orig);
    (指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位
    的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))
    llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
    loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示.
    如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器。

    ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t * p); (指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址), 参数size为要读取的信息长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值) 这个函数用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).

    ssize_t (*aio_read)(struct kiocb * , char __user * buffer, size_t size , loff_t   p); 可以看出,这个函数的第一、三个参数和本结构体中的read()函数的第一、三个参数是不同 的, 异步读写的第三个参数直接传递值,而同步读写的第三个参数传递的是指针,因为AIO从来不需要改变文件的位置。 异步读写的第一个参数为指向kiocb结构体的指针,而同步读写的第一参数为指向file结构体的指针,每一个I/O请求都对应一个kiocb结构体); 初始化一个异步读 -- 可能在函数返回前不结束的读操作.如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地). (有关linux异步I/O,可以参考有关的资料,《linux设备驱动开发详解》中给出了详细的解答)

    ssize_t (*write) (struct file * filp, const char __user *   buffer, size_t count, loff_t * ppos); (参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度, ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界) 发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数. (注:这个操作和上面的对文件进行读的操作均为阻塞操作)

    ssize_t (*aio_write)(struct kiocb *, const char __user * buffer, size_t count, loff_t * ppos);       初始化设备上的一个异步写.参数类型同aio_read()函数;

    int (*open) (struct inode * inode , struct file * filp ) ; (inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构; 但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息) 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知. 与open()函数对应的是release()函数。

    int (*flush) (struct file *); flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.

    int (*release) (struct inode *, struct file *); release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数: void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。     在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.

    知识点三:(未完待续...)

  • 相关阅读:
    Document
    Document
    Document
    Document
    Document
    Document
    Document
    Document
    C#中的委托是什么
    C# Lambda表达式Contains方法 like
  • 原文地址:https://www.cnblogs.com/starsKing/p/6098664.html
Copyright © 2020-2023  润新知