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 }

纯净版

 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 }

知识点一 :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;                             })

知识点二: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 };

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.

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

时间: 2024-10-25 21:24:23

linux 驱动学习(一)简单的字符设备驱动程序的相关文章

0915-----Linux设备驱动 学习笔记----------一个简单的字符设备驱动程序

0.前言 研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧! 1.准备工作 a)查看内核版本 uname -r b)安装内核源码树(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html) 在www.linux.org上下载源码编译,这里是.xz格式,需要安装解压工具,xz-utils: 解压方法示例:xz -d linux-3.1-rc4.

linux驱动开发(三) 字符设备驱动框架

还是老规矩先上代码 demo.c #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/cdev.h> #include <linux/fs.h> int demo_major = 250; int demo_minor = 0; int demo_count = 1; struct cdev cdev; int de

linux驱动开发(三) 字符设备驱动框架(自动创建设备节点)

代码如下 #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> int demo_major = 250; int demo_minor = 0; int demo_count = 1; stru

linux设备驱动第三篇:写一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [plain] vie

linux设备驱动第三篇:如何写一个简单的字符设备驱动?

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [email prot

linux设备驱动第三篇:如何实现简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动 都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [email pro

linux设备驱动第三篇:如何实现一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: ? 1 2 3 4 5

linux驱动学习(1)——字符设备驱动开发

(一)驱动程序介绍 (a)Linux驱动程序学习 知识结构: 1. Linux驱动程序设计模式(40%) 2. 内核相关知识(30%) 3. 硬件相关知识(30%) (b)驱动分类: ①字符设备: 字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现 open, close,read和 write 系统调用. ②块设备: 在大部分的 Unix 系统, 块设备不能按字节处理数据,只能一次传送一个或多个长度是512字节( 或一个更大的 2 次幂的数 )的整块数据,而Lin

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

字符设备驱动程序 应用程序是调用C库中的open read write等函数.而为了操作硬件,所以引入了驱动模块. 构建一个简单的驱动,有一下步骤. 1. 创建file_operations 2. 申请设备号 3. 注册字符设备驱动, 4. 驱动入口 5. 驱动出口 检查数据是否到来的方式: 1. 查询方式 2. 休眠唤醒方式 如果设备出现异常而无法唤醒时,则将永远处于休眠状态. 3. poll机制 如果没有被唤醒,则在一定时间内可自己唤醒. 4. 异步通知(信号) 而以上的几种方式通用性不高,