Linux驱动程序-----字符设备

原创 2015年07月10日 15:45:42

根据书本<Linux设备驱动开发详解>,写了自己的一个Linux驱动,一个简单的字符设备,其实也没什么实际功能,主要是能对字符设备类驱动有一个很清晰的认识,也增加了自己对驱动程序开发的信心吧

1、重要的结构体分析

1.1  cdev结构体

struct cdev
{
  struct kobject kobj;//内嵌的kobject对象
  struct module *owner;//所属模块
  struct file_operations *ops;//文件操作结构体
  struct list_head list;
  dev_t dev;//设备号
  unsigned int count;
};

cdev 结构体的 dev_t 成员定义了设备号,为 32 位,其中高 12 位为主设备号,低20 位为次设备号。使用下列宏可以从dev_t中生成主次设备号

        主设备号:MAJOR(dev_t dev);

        次设备号:MINOR(dev_t dev);

 使用下列宏则可以通过主次设备号生成dev_t  

MKDEV(int major,int minor); 

cdev另一个重要成员就是file_operations定义了字符设备驱动程序提供给虚拟文件系统的接口函数,也是字符设备驱动程序的主要组成部分。cdev结构体的操作函数有:

void cdev_init(struct cdev *, struct file_operations *); 

int cdev_add(struct cdev *, dev_t, unsigned);

viod cdev_del(struct cdev *);

cdev_init()函数用于初始化cdev的各个成员,并建立cdev与file_operations 之间的连接,cdev_add()和cdev_del()分别是用于向系统添加和删除一个cdev,完成字符设备的注册和消除。


1.2  file_operations结构体

file_operations是实现系统调用的具体操作

1  struct file_operations 
2  { 
3    struct module *owner; 
4      // 拥有该结构的模块的指针,一般为 THIS_MODULES 
5    loff_t(*llseek)(struct file *, loff_t, int); 
6      // 用来修改文件当前的读写位置  
7    ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*); 
8      // 从设备中同步读取数据 
9    ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t); 
10     // 初始化一个异步的读取操作
11   ssize_t(*write)(struct  file  *,  const  char  _ _user  *,  size_t, loff_t*); 
12     // 向设备发送数据 
13   ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t); 
14     // 初始化一个异步的写入操作 
15   int(*readdir)(struct file *, void *, filldir_t); 
16     // 仅用于读取目录,对于设备文件,该字段为 NULL 
17   unsigned int(*poll)(struct file *, struct poll_table_struct*); 
18     // 轮询函数,判断目前是否可以进行非阻塞的读取或写入 
19   int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long); 
20     // 执行设备 I/O 控制命令 
21   long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long); 
22     // 不使用 BLK 文件系统,将使用此种函数指针代替 ioctl 
23   long(*compat_ioctl)(struct file *, unsigned int, unsigned long); 
24     // 在 64 位系统上,32 位的 ioctl 调用将使用此函数指针代替 
25   int(*mmap)(struct file *, struct vm_area_struct*); 
26     // 用于请求将设备内存映射到进程地址空间 
27   int(*open)(struct inode *, struct file*); 
28     // 打开 
29   int(*flush)(struct file*); 
30   int(*release)(struct inode *, struct file*); 
31     // 关闭 
32   int(*synch)(struct file *, struct dentry *, int datasync); 
33     // 刷新待处理的数据 
34   int(*aio_fsync)(struct kiocb *, int datasync); 
35     // 异步 fsync 
36   int(*fasync)(int, struct file *, int); 
37     // 通知设备 FASYNC 标志发生变化 
38   int(*lock)(struct file *, int, struct file_lock*); 
39   ssize_t(*readv)(struct file *, const struct iovec *, unsigned long, loff_t*); 
40   ssize_t(*writev)(struct file *, const struct iovec *, unsigned long, loff_t*); 
41     // readv 和 writev:分散/聚集型的读写操作 
42   ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void*); 
43     // 通常为 NULL 
44   ssize_t(*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int); 
45     // 通常为 NULL 
46   unsigned long(*get_unmapped_area)(struct file *,unsigned long, unsigned long, 
47     unsigned long, unsigned long); 
48     // 在进程地址空间找到一个将底层设备中的内存段映射的位置 
49   int(*check_flags)(int); 
50     // 允许模块检查传递给 fcntl(F_SETEL...)调用的标志 
51   int(*dir_notify)(struct file *filp, unsigned long arg); 
52     // 仅对文件系统有效,驱动程序不必实现 
53   int(*flock)(struct file *, int, struct file_lock*);  
54 }; 

1.3  注册和释放设备号
160     dev_t devno = MKDEV(xxx_major, 0);                           //已知主设备号,生成dev_t         
161                                                                                
162     if (xxx_major)
163         result = register_chrdev_region(devno, 1, DRIVER_NAME);  //静态注册,             
164     else {
165         result = alloc_chrdev_region(&devno, 0, 1, DRIVER_NAME); //动态注册,设备号保存在devno               
166         xxx_major = MAJOR(devno);                                //生成主设备号         
167     }
168     if (result < 0)                                                            
169         return result;

2、驱动程序结构分析

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include   //kmalloc() and kfree()
#include 
#include 
#include 



int xx_open(struct inode *inode, struct file *filp)
{
}

int xx_release(struct inode *inode, struct file *filp)
{
}

static int xx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	case xx0:
		break;
	case xx1:
	    break;
	default:
		return -EINVAL;
	}	
	return 0;
}

static ssize_t xx_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    ......
	if (copy_to_user(buf, , )
	......
	return xx;
}

static ssize_t xx_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    ......
		
	if(copy_from_user(, buf, )
    ......
    return xx;
}

static loff_t xx_llseek(struct file *filp, loff_t offset, int orig)
{
    ......
	return ret;
}

static const struct file_operations xx_fops = {
	.owner   = THIS_MODULE,
	.llseek  = xx_llseek,
	.read    = xx_read,
	.write   = xx_write,
	.ioctl   = xx_ioctl,
	.open    = xx_open,
	.release = xx_release,
};

int xx_init(void)
{
}

int xx_exit(void)
{
}


module_init(xx_init);
module_exit(xx_exit);


MODULE_AUTHOR("Liu.Hongbo");
MODULE_LICENSE("Dual BSD/GPL");

3、具体程序实现

该例程是一个简单的memory程序,就把一块内存虚拟成一个字符设备,对其进行读写操作。代码简易,对字符驱动程序有个很清楚的概括

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define GLOBALMEM_SIZE   0x1000
#define MEM_CLEAR        0x1
#define GLOBALMEM_MAJOR  250
#define DRIVER_NAME      "globalmem"

static int globalmem_major = GLOBALMEM_MAJOR;

struct globalmem_dev {
	struct cdev cdev;
	unsigned char mem[GLOBALMEM_SIZE];
};

struct globalmem_dev *gm_devp;

int gm_open(struct inode *inode, struct file *filp)
{
	filp->private_data = gm_devp;
	return 0;
}

int gm_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static int gm_ioctl(struct inode *inode, 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;
}

static ssize_t gm_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 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 %u bytes for %lu\n", count, p);
	}
	return ret;
}

static ssize_t gm_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 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 %u bytes from %lu\n",count , p);
	}
	return ret;
}

static loff_t gm_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 += (unsigned int)offset;
		ret = filp->f_pos;
		break;

	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

static const struct file_operations globalmem_fops = {
	.owner   = THIS_MODULE,
	.llseek  = gm_llseek,
	.read    = gm_read,
	.write   = gm_write,
//	.ioctl   = gm_ioctl,
	.open    = gm_open,
	.release = gm_release,
};

static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
	int err, devno = MKDEV(globalmem_major, index); //create dev_t = devno
	cdev_init(&dev->cdev, &globalmem_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cdev, devno, 1);
	if (err)
		printk(KERN_NOTICE "Error %d adding globalmem %d", err, index);
}

int globalmem_init(void)
{
	int result;
	dev_t devno = MKDEV(globalmem_major, 0);
	
	if (globalmem_major)
		result = register_chrdev_region(devno, 1, DRIVER_NAME);
	else {
		result = alloc_chrdev_region(&devno, 0, 1, DRIVER_NAME);
		globalmem_major = MAJOR(devno);
	}
	if (result < 0)
		return result;
	
	gm_devp = kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
	if (!gm_devp) {
		result = -ENOMEM;
		goto fail_malloc;
	}
	memset(gm_devp, 0, sizeof(struct globalmem_dev));

	globalmem_setup_cdev(gm_devp, 0);
	return 0;
fail_malloc:
	unregister_chrdev_region(devno, 1);
	return result;
}

int globalmem_exit(void)
{
	cdev_del(&gm_devp->cdev);
	kfree(gm_devp);
	unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}


module_init(globalmem_init);
module_exit(globalmem_exit);


MODULE_AUTHOR("Liu.Hongbo");
MODULE_LICENSE("Dual BSD/GPL");

Makefile如下

obj-m := globalmem.o
  2 
  3 KERNEL_DIR := /lib/modules/$(shell uname -r)/build
  4 
  5 PWD := $(shell pwd)
  6 
  7 all:  
  8     make -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules  
  9 
 10 clean:  
 11     rm *.o *.ko *.mod.c  
 12 
 13 .PHONY:clean
 14 



----------------------测试----------------------------

$:sudo su
#:insmod globalmem.ko
#:mknod /dev/globalmem c 250 0
#:echo "Hello World" >/dev/globalmem
#cat /dev/globalmem
屏幕上显示Hello World 驱动测试Ok



版权声明:本文为博主原创,如有雷同,不胜荣幸!^_^

Linux驱动程序开发 - 字符设备驱动

Linux下的大部分驱动程序都是字符设备驱动程序

基于mini6410的linux驱动学习总结(四 设计字符设备驱动程序)

涉及的知识点 1、设备号 2、创建设备文件 3、重要数据结构 4、设计字符设备驱动的步骤   1.设备号 设备号用来做什么? 设备号作用:主设备号用来标识与设备文件相连的驱动程序。次编...

Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

字符设备模拟pipe的驱动程序 让我们用一个”pipe“的设备驱动去结束简单字符设备吧(这里所说的pipe并非标准的pipe只是模拟了一个从一端写入从另一端写入的设备) 测试代码1      测试...

Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

字符设备模拟pipe的驱动程序 让我们用一个”pipe“的设备驱动去结束简单字符设备吧(这里所说的pipe并非标准的pipe只是模拟了一个从一端写入从另一端写入的设备) 测试代码1    ...

浅谈linux驱动之-字符设备驱动程序

嵌入式爱好者群 122879839 大概分以下五个知识点: 1.设备号 2.创建设备文件 3.设备注册 4.重要数据结构 5.设备操作 首先我们谈谈设备号,其实设...

Linux驱动程序开发之字符设备驱动——基础篇(二)

Linux驱动程序开发之字符设备驱动——基础篇(二) 转自:http://www.cnblogs.com/LakeFollow/archive/2012/07/30/2614475.html ...

Linux下字符设备驱动程序的结构

  • 2011年10月21日 23:18
  • 41KB
  • 下载

Linux字符设备驱动程序的编写框架

一、Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用...

Linux字符设备驱动程序分析与设计

  • 2015年06月08日 11:33
  • 1.86MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux驱动程序-----字符设备
举报原因:
原因补充:

(最多只允许输入30个字)