第一部分 必要的设备结构体
1)linux 2.6内核中使用cdev结构体表示字符设备: struct cdev { struct kobject kobj;//内嵌的kobject对象 struct module *owner;//所属模块 struct file_operations *ops;//文件操作结构体 struct list_head list; dev_t dev;//设备号,长度为32位,其中高12为主设备号,低20位为此设备号 unsigned int count; };
可以使用下列宏从dev_t中获得主次设备号: 也可以使用下列宏通过主次设备号生成dev_t: MAJOR(dev_t dev); MKDEV(int major,int minor); MINOR(dev_t dev); 说明:在2.6内核中可以容纳大量的设备,而先前的内核版本却限于255个主设备号和255个此设备号。 2)file_operations结构体中的成员函数是字符设备驱动程序设计中的主体内容,这些函数实际会在应用程序进行linux的open(),write(),read(),close()等系统调用时被最终调用。目前的file_operations结构已经变得非常大,在这里我们就关心和我这个设备程序有关的几个函数,以后用到了,咱们再提也不迟:
static const struct file_operations globalmem_fops = { .owner= THIS_MODULE, .llseek = globalmem_llseek,//修改一个文件的当前读写位置并将新位置返回,出错时,返回一个负值 .read = globalmem_read, .write = globalmem_write, .ioctl = globalmem_ioctl,//设备相关控制命令的实现,内核可以识别一部分控制命令(这时就不用调用ioctl ()),如果设备不提供这个函数,而内核又不识别该命令,则返回-EINVAL. .open = globalmem_open, .release = globalmem_release, };
3)file结构与用户空间的File没有任何关联,strcut file是一个内核结构,它不会出现在用户程序中。它代表一个打开的文件(不局限于设备驱动程序,系统中每个打开的文件在内核空间中都有一个对应的file结构)。它由内核在open时创建,并传递给在该文件进行操作的所有函数,直到最后的close函数。在文件的所有实例都被关闭之后,内核会释放掉这个数据结构。 4)内核用它node结构在内部表示文件,其和file结构不同。后者表示打开的文件描述符。对于单个文件,可能会有很多表示打开的文件描述符的file结构,但它们都指向单个inode结构。在它里边和我们驱动程序有用的字段只有两个: dev_t i_rdev; //对表示设备文件的inode结构,该字段包含了真正的设备编号 struct cdev *i_cdev; //是表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。 我么可以使用下边两个宏从inode中获得主设备号和此设备号:
unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);
为了程序的可移植性,我们应该使用上述宏,而不是直接操作i_rdev。
第二部分 源代码详解
转存失败重新上传取消#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h>
#define GLOBALMEM_SIZE 0X1000 /*全局内存大小4kb*/
#define MEM_CLEAR 0x1 //清零全局内存
static globalmem_major = GLOBALMEM_MAJOR;
//globalmem设备结构体 struct globalmem_dev { struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE];//全局内存 };
struct globalmem_dev *globalmem_devp;//设备结构指针 //文件打开函数 int globalmem_open(struct inode *inode, struct file *filp) { filp->private_data= globalmem_devp; return 0; } //文件释放函数 int globalmem_release(struct inode *inode, struct file *filp) { return 0; } //globalmem_ioctl函数 static int globalmem_ioctl(struct inode *inodep, struct file *filp,unsigned int cmd, unsigned long arg) { struct globalmem_dev *dev = filp->private_data; switch (cmd) { case MEM_CLEAR://清除全局内存 memset(dev->mem, 0,GLOBALMEM_SIZE); printk(KERN_INFO "globalmem is set to zero\n"); break; default: return - EINVAL;//其他不支持的命令 } return 0; } //globalmem_read函数 static ssize_t globalmem_read(struct file *filp,char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev=filp->private_data;//获得设备结构指针 //分析和获取有效的写长度 if (p >= GLOBALMEM_SIZE) return count ? -ENXIO: 0; if (count > GLOBALMEM_SIZE-p)//如果要求读取的比实际可用的少 count = GLOBALMEM_SIZE-p; if (copy_to_user(buf,(void *)(dev->mem + p),count)) { ret = -EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "read %d byte(s) from %d",count,p); } return ret; } //globalmem_write static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data;//获得设备结构指针 //分析和获取有效的写长度 if( p >= GLOBALMEM_SIZE) return count ? -ENXIO: 0; if (count > GLOBALMEM_SIZE-p)//如果要求读取的比实际可用的少 count = GLOBALMEM_SIZE-p; if (copy_from_user(dev->mem + p,buf, count)) ret = -EFAULT; else { *ppos+= count; ret = count; printk(KERN_INFO "written %d bytes(s) from %d\n", count, p); } return ret; } //globalmem_seek函数 static loff_t globalmem_llseek(struct file *filp,loff_t offset,int orig) { loff_t ret=0; switch(orig) { case 0://从文件开头开始偏移 if(offset < 0) { ret = - EINVAL; break; } if((unsigned int)offset > GLOBALMEM_SIZE)//偏移越界 { ret = - EINVAL; break; } filp->f_pos= (unsigned int)offset; ret = filp->f_pos; break; case 1://从当前位置偏移 if((filp->f_pos+offset) > GLOBALMEM_SIZE) //偏移越界 { ret = - EINVAL; break; } if((filp->f_pos+offset)<0) { ret = - EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = - EINVAL; break; } return ret; }
以上介绍了Linux简单字符设备中涉及到的基本而要特别重要的数据结构,还有就是源代码部分中有关file_operations中的所有操作,这些都将在应用程序进行Linux的