1.关于设备文件的介绍(翻译自Understanding the Linuxkernel)
设备文件
在类Unix系统中基于一个概念:一切皆文件,文件就像是包含一系列数据的信息容器。因此,I/O设备在类Unix系统中被当成特殊的文件进行处理,称为设备文件;系统调用可以同时操作设备文件和普通文件。例如,write函数可以向普通文件中写入数据,也能通过向/dev/lp0设备文件中写入数据将数据发送至打印机。
根据设备驱动的特性,设备文件可以分为两大类:块设备文件和字符设备文件。这两种硬件设备的分类可能没那么明确,但是我们可以认为其区别如下:
块设备上的数据可以随机存取,传输一个数据块时间很短且几乎相同,传统的块设备有硬盘,软盘,CD-ROM,DVD等
对于块设备的数据的操作,理论上来说是不支持随机读写的(例如声卡),即便能够随机读写,它们读取设备文件中特定数据的时间极大地依赖于其在设备的位置(如磁带播放器)
网卡设备不适用此分类,因为网卡设备不直接和设备文件打交道。
在早期的类Unix操作系统中就已经有设备文件了,设备文件通常是文件系统中真实的文件,设备文件的inode节点都包含了相关字符设备和块设备的标识符,inode节点中没有指向硬盘数据的指针(文件数据),因为它们根本没有文件数据。
通常,设备文件标识符包括设备文件的类型(字符设备或块设备)以及设备号。设备号又分为主设备号和次设备号,主设备号表示设备类型,相同类型设备主设备号相同,且共享一套文件操作方法(file operation)。次设备号用于区分多个相同类型设备。
类Unix操作系统中可以用mknod()系统调用来创建设备文件,其参数有设备类型(c代表字符设备,b代表块设备),主设备号和次设备号。设备文件通常位于目录/dev/下,值得注意的是字符设备和块设备的设备号是相互独立的,例如字符设备(3,0)和块设备(3,0)两者是完全不同的两个设备。
设备文件通常都对应真实的硬件设备(如硬盘设备文件/dev/hda),或硬件设备的物理或逻辑分区(如硬盘分区文件/dev/hda2)。然而,有些情况下,设备文件并不是对应真实的物理设备而是虚拟的逻辑设备,例如/dev/null设备文件,其作用是丢弃所有的输入数据,因此该文件总是为空。
字符设备驱动
操作一个字符设备相对比较容易,它没有块设备中的缓冲机制和硬盘缓存。然而,根据不同的功能,字符设备之间也会大相径庭,有的字符设备需要一套复杂的通讯协议来操作设备,而有的字符设备却只需要从I/O口中读取几个字符,如一个多串口卡设备的设备驱动就远比一个USB鼠标复杂。
2.自己实现一个简单的字符设备驱动
在这个字符设备驱动中,我一共实现了四个接口分别为open,read,write以及release函数,在上层应用中可通过open,read,write以及close函数调用,模块的入口是mychrdev_init函数,即加载模块时调用的函数,函数中通过register_chrdev函数注册该字符设备驱动,unregister_chrdev函数卸载该字符设备驱动。下面分别贴上设备驱动源代码以及测试文件代码
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/device.h>
#include <linux/moduleparam.h>
#include <asm/uaccess.h>
#include <linux/kmod.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <asm/traps.h>
static char buf[32];
static int major;
static char Device_open;
#define ERROR -1;
static int mychrdev_open(struct inode *inode,struct file *filep){
if(Device_open){
return -EBUSY;
}
Device_open++;
printk("The major number of the device is %d\n", MAJOR(inode->i_rdev));
printk("The minor number od the device is %d\n", MINOR(inode->i_rdev));
printk("<0>module_refcount(module):%d\n",module_refcount(THIS_MODULE));
try_module_get(THIS_MODULE);
printk("<0>module_refcount(module):%d\n",module_refcount(THIS_MODULE));
return 0;
}
static ssize_t mychrdev_read(struct file *fillp, char __user *data, size_t count, loff_t *off){
#ifdef DEBUG
printk(KERN_INFO "device_read(%p,%p,%d)\n", filep, buf, count);
#endif
printk(KERN_INFO "entering the read function\n");
//printk("%s\n",data);
if(copy_to_user(data,buf,sizeof(buf))){
printk("cannot copy data to usersapce\n");
return ERROR;
}
#ifdef DEBUG
printk(KERN_INFO "Read %d bytes, %d left\n", i, count);
#endif
return 0;
/*if(copy_to_user(data,buf,sizeof(buf))){
printk("cannot copy data to usersapce\n");
return ERROR;
}
return 0;*/
}
static ssize_t mychrdev_write(struct file *filp, const char __user *data, size_t count, loff_t *off){
//static int i;
#ifdef DEBUG
printk(KERN_INFO "device_write(%p,%p,%d)\n", filep, buffer, count);
#endif
printk("entering the write function\n");
printk("%s\n",data);
if(copy_from_user(buf,data,sizeof(data)))
{ printk("cannot copy data from userspace\n");
return ERROR;
}
return 0;
}
static int mychrdev_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
printk(KERN_INFO "device_release(%p,%p)\n", inode, file);
#endif
Device_open--;
module_put(THIS_MODULE);
return 0;
}
struct file_operations my_op={
.owner=THIS_MODULE,
.open=mychrdev_open,
.read=mychrdev_read,
.write=mychrdev_write,
.release=mychrdev_release,
};
static int mychrdev_init(void)
{
static int major;
major = register_chrdev(0,"zhaichuan",&my_op);
if (major < 0) {
printk(KERN_ERR "unable to get major %d\n", major);
return major;
}
printk(KERN_INFO "%s The major device number is %d.\n",
"Registeration is a success", major);
printk(KERN_INFO "please use mknod %s c %d 0 to create a device file\n", "zhaichuan", major);
return 0;
}
static int mychrdev_exit(void)
{ //static int ret;
unregister_chrdev(major, "zhaichuan");
/*
* If there's an error, report it
*/
printk("<0>module_refcount(module):%d\n",module_refcount(THIS_MODULE));
return 0;
}
module_init(mychrdev_init);
module_exit(mychrdev_exit);
MODULE_LICENSE("GPL");
测试文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
//#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <io.h>
#include <asm-generic/ioctl.h>
#include <asm-generic/fcntl.h>
#define LENGTH 80
char buffer[LENGTH];
/*int strlen(char *s){
int n=0;
while(s++){
n++;
}
return n;
}*/
int main(int argc,char* argv[])
{static int i;
static int ret;
static int ret2;
static char *s;
char *m="LInux character device";
//memcpy(s,m,strlen(m));
int fd;
//fd = open("/home/zhaichuan/char.c",O_WRONLY);
//printf("%d\n",fd);
fd=open("/dev/zhaichuan",O_WRONLY);
printf("%d\n",fd);
if(fd==-1){
printf("cannot open device\n");
return 0;
}
printf("CHARDEV open successfully\n");
ret2=write(fd,m,10);
printf("The return value of write function is %d\n",ret2);
printf("CHARDEV write successfully\n");
close(fd);
printf("%s\n",m);
fd=open("/dev/zhaichuan",0);
ret=read(fd,buffer,sizeof(buffer));
printf("The return value of read function is %d\n,and the returning value of fd is %d\n",ret,fd);
close(fd);
printf("%s\n",buffer);
return 0;
}
调试时遇到的问题:
1.测试文件执行时,write函数返回-1,解决方法:在执行测试文件时,一定要获得管理员权限,如sudo ./mychar.c,不然文件是不能写入的,之前一直纠结了很久。
2.文件中定义了的函数未包含头文件,解决办法:可以在头文件目录中使用grep命令搜索相对应的函数名,找到对应的头文件名,而后再包含进你的文件中。
3.多用print命令打印调试,我的文件中有很多的printf/printk,很多都是为了调试而后写入文件的。