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 }
纯净版
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.
知识点三:(未完待续...)
以上是关于linux 驱动学习简单的字符设备驱动程序的主要内容,如果未能解决你的问题,请参考以下文章
Linux驱动入门-最简单字符设备驱动(基于pc ubuntu)
Linux驱动入门-最简单字符设备驱动(基于pc ubuntu)