linux 驱动学习简单的字符设备驱动程序

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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!\\n");
37         return -1;
38     }
39     else
40     {
41         printk("the device has been registered!\\n");
42         device_num = ret;
43         printk("<1>the virtual device‘s major number %d.\\n", device_num);
44         printk("<1>Or you can see it by using\\n");
45         printk("<1>------more /proc/devices-------\\n");
46         printk("<1>To talk to the driver,create a dev file with\\n");
47         printk("<1>------‘mknod /dev/myDevice c %d 0‘-------\\n", device_num);
48         printk("<1>Use \\"rmmode\\" to remove the module\\n");
49 
50         return 0;
51     }
52 }
53 /* 注销模块,函数名很特殊 */
54 void cleanup_module()
55 {
56     unregister_chrdev(device_num, devName);
57     printk("unregister it success!\\n");
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\\n", MAJOR(inode->i_rdev));
66     printk("<1>slave device : %d\\n", MINOR(inode->i_rdev));
67     printk("<1>%d times to call the device\\n", ++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!\\n");
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!\\n");
37         return -1;
38     }
39     else
40     {
41         printk("the device has been registered!\\n");
42         device_num = ret;
43         printk("<1>the virtual device‘s major number %d.\\n", device_num);
44         printk("<1>Or you can see it by using\\n");
45         printk("<1>------more /proc/devices-------\\n");
46         printk("<1>To talk to the driver,create a dev file with\\n");
47         printk("<1>------‘mknod /dev/myDevice c %d 0‘-------\\n", device_num);
48         printk("<1>Use \\"rmmode\\" to remove the module\\n");
49 
50         return 0;
51     }
52 }
53 /* 注销模块,函数名很特殊 */
54 void cleanup_module()
55 {
56     unregister_chrdev(device_num, devName);
57     printk("unregister it success!\\n");
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\\n", MAJOR(inode->i_rdev));
66     printk("<1>slave device : %d\\n", MINOR(inode->i_rdev));
67     printk("<1>%d times to call the device\\n", ++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!\\n");
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

 

 

     


知识点一 :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\\n", 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.

 

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

 


















以上是关于linux 驱动学习简单的字符设备驱动程序的主要内容,如果未能解决你的问题,请参考以下文章

Linux驱动入门-最简单字符设备驱动(基于pc ubuntu)

Linux驱动入门-最简单字符设备驱动(基于pc ubuntu)

字符设备研究_1

4412开发板Linux系统编程实战-字符设备控制

Linux嵌入式驱动学习之路(二十一)字符设备驱动程序总结和块设备驱动程序的引入

字符设备驱动程序