Linux字符驱动

http://blog.csdn.net/aaa6695798/article/details/4599918


目录

  [隐藏]

[编辑]字符设备驱动知识讲解

作者:[牛涛]

[编辑]描述字符设备基本结构体

/linux/include/linux/cdev.h

 13struct cdev {
 14        struct kobject kobj;
 15        struct module *owner;
 16        const struct file_operations *ops;
 17        struct list_head list;
 18        dev_t dev;
 19        unsigned int count;
 20};

[编辑]作用

描述字符设备的基本结构体,用于进一步封装在更大的结构体内,以描述具体的字符设备,例如:

struct xxx_cdev {
const char *name;
struct cdev cdev;
...
}

这样就可以使用xxx_cdev结构描述具体的字符设备了。

[编辑]各字段详解

1、struct kobject kobj;

具体结构参见http://blog.chinaunix.net/u1/55599/showart.php?id=1086478,此处它是一个内嵌的结构,作用是提供引用计数。在调用cdev_init()函数初始化cdev结构体时,会调用kobject_init()函数初始化该数据域。具体见下文cdev_init()函数。

2、struct module *owner;

指出该字符设备所属的模块,一般初始化为THIS_MODULES。

3、struct file_operations *ops;

文件操作指针,也是该结构中最重要的数据域。字符设备驱动的编写主要就是实现file_operations里的一些常用函数,比如read,write,open,release等。关于file_operations结构的详解参见:http://blog.chinaunix.net/u2/73521/showart_1086491.html

4、struct list_head list;

与字符设备文件对应的索引节点链表头,用于收集相同字符设备驱动程序所对应的字符设备文件的索引节点。可能很多设备文件具有相同的设备号,并对应于相同的字符设备。该字段在cdev_alloc()和cdev_init()中进行了初始化。

5、dev_t dev;

代表该字符设备的设备号。dev_t实际上为unsigned int类型,32位。其中高12位为主设备号,低20位为次设备号。如果已知一个设备号,可以使用一下宏分别取得主设备号和次设备号:

MAJOR(dev_t dev) /*取得主设备号*/

MINOR(dev_t dev) /*取得次设备号*/

这两个宏的原型是:/linux/include/linux/kdev_t.h

  4#define MINORBITS       20
  5#define MINORMASK       ((1U << MINORBITS) - 1)
  6
  7#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))
  8#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))

可见两个宏都是通过位运算实现的。还有如果已知主设备号和次设备号,可以使用MKDEV()宏求得设备号:

  1. define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

6、unsigned int count;

表示设备号范围大小。一个设备驱动程序对应的设备号可以是一个范围,而不仅仅是一个号,设备号位于同一范围内的所有设备文件均由同一个字符设备驱动程序处理。

[编辑]操作

1、struct cdev * cdev_alloc(void);linux/fs/char_dev.c

509struct cdev *cdev_alloc(void)

510{
511        struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
512        if (p) {
513                p->kobj.ktype = &ktype_cdev_dynamic;
514                INIT_LIST_HEAD(&p->list);
515                kobject_init(&p->kobj);
516        }
517        return p;
518}

很显然从函数名和返回值可知该函数的作用是分配并返回一个cdev结构。调用kzalloc()函数申请sizeof(struct cdev)大小的空间并初始化为0,然后检查如果分配成功,则继续进行初始化工作。首先是初始化p->kobj.ktype域为ktype_cdev_dynamic。我们来看看ktype_cdev_dynamic究竟为何物:

489static void cdev_dynamic_release(struct kobject *kobj)
490{
491        struct cdev *p = container_of(kobj, struct cdev, kobj);
492        cdev_purge(p);
493        kfree(p);
494}
500static struct kobj_type ktype_cdev_dynamic = {
501        .release        = cdev_dynamic_release,
502};

可见是用来释放kzalloc()申请的空间的。然后初始化双向循环链表list和内嵌的kobj。最后返回一个cdev结构。如果加上对file_operations域的初始化,则该函数就相当于cdev_init()。

2、void cdev_init(struct cdev *,struct file_operations *);/linux/fs/char_dev.c

528void cdev_init(struct cdev *cdev, const struct file_operations *fops)
529{
530        memset(cdev, 0, sizeof *cdev);
531        INIT_LIST_HEAD(&cdev->list);
532        cdev->kobj.ktype = &ktype_cdev_default;
533        kobject_init(&cdev->kobj);
534        cdev->ops = fops;
535}

函数的作用是初始化cdev,并建立cdev和file_operations之间的连接。在调用cdev_init()函数之前,cdev结构已经存在,此处先将其初始化为0,接下来进行和cdev_alloc()的初始化相同的动作,最后将传入的文件操作结构体指针fops赋值给file_operations域。ktype_cdev_default和前面讲的ktype_cdev_dynameic处理基本相同,具体参见:

/linux/fs/char_dev.c

3、int cdev_add(struct cdev *, dev_t , unsigned );/linux/fs/char_dev.c

457int cdev_add(struct cdev *p, dev_t dev, unsigned count)

458{
459        p->dev = dev;
460        p->count = count;
461        return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
462}

向系统添加一个字符设备,并且立刻置于活动状态,如果返回一个负值则表示添加失败。由代码可知,调用cdev_add()函数之前,必须已经分配好设备号,并且确定了设备范围。kobj_map()函数的第一个参数cdev_map是被定义在/linux/fs/char_dev.c中的一个kobj_map类型的指针,exact_math和exact_lock是两个函数,具体参见/linux/fs/char_dev.c。

4、void cdev_del(struct cdev *);/linux/fs/char_dev.c

476void cdev_del(struct cdev *p)
477{
478        cdev_unmap(p->dev, p->count);
479        kobject_put(&p->kobj);
480}

从系统删除一个字符设备。我们先来看一下cdev_unmap():

464static void cdev_unmap(dev_t dev, unsigned count)
465{
466        kobj_unmap(cdev_map, dev, count);
467}


cdev_unmap()函数只不过是对kobj_unmap()进行了封装而已。kobj_unmap()对应注册字符设备函数cdev_add()中的kobj_map(),kobject_put()减少引用计数对应cdev_init()中的调用kobject_init()初始化引用计数为1。可见cdev_del()做和cdev_add()完全相反的动作。

5、

int register_chrdev_region(dev_t from, unsigned count, const char *name);

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

const char *name);

void unregister_chrdev_region(dev_t from, unsigned count);

前两个为向系统申请设备号。register_chrdev_region()用在已知其实设备号的情况,如果设备号未知,则应使用alloc_chrdec_region()函数申请,系统会从254开始寻找未被使用的主设备号,如果找到了并成功分配了,则生成一个设备号放在第一个参数dev中,返回0,否则返回-EBUSY(资源繁忙)。unregister_chrdev_region()函数用于释放原先申请的设备号。

此三个函数具体参见:http://lxr.linux.no/linux/fs/char_dev.c

6、

int register_chrdev(unsigned int major, const char *name,

const struct file_operations *fops);

void unregister_chrdev(unsigned int major, const char *name);

这两个函数可以说是大大的方便了字符驱动程序的注册和注销过程。

在register_chrdev()函数中,如果参数major为0,则系统动态为其分配一个设备号。如果major大于0则按照major给出的主设备号给其分配一个设备号。设备号分配成功后,register_chrdev()调用cdev_alloc()分配一个cdev结构,并进行初始化,建立cdev和file_operations之间的连接。之后调用cdev_add()向系统注册字符设备。对于它的返回值,在注册成功时,如果参数major为0则返回动态申请的设备号,否则返回0。在注册失败时返回错误码。

在unregister_chrdev()函数中,参数major就是你先前给出的主设备号,name为字符设备名(实际上没有使用),系统就会注销注册的字符设备。

在调用这两个函数时,没有牵扯到使用cdev结构,程序员只需要给出想要该字符设备做什么和怎样做(实现file_operations),给出该字符设备的主设备号或者不给就可以写出字符驱动程序了。

[编辑]实例

[编辑]代码

/*globalmem.c*/</br>

#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 /*清零全局内存*/
#define GLOBALMEM_MAJOR 252 /*预设的globalmem的主设备号*/

static int globalmem_major = GLOBALMEM_MAJOR;
struct globalmem_dev {
	struct cdev cdev;
	unsigned char mem[GLOBALMEM_SIZE];
	struct semaphore sem;
};
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)
{
	filp->private_data = NULL;
	return 0;
}

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:
		if(down_interruptible(&dev->sem))
			return -ERESTARTSYS;
		memset(dev->mem,0,GLOBALMEM_SIZE);
		up(&dev->sem);
		printk(KERN_INFO"globalmem is set to zero/n");
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

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){
		printk(KERN_INFO"globalmem read here!");
		return count ? -ENXIO : 0;
	}

	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;
	if(down_interruptible(&dev->sem))
		return -ERESTARTSYS;
	if(copy_to_user(buf,(void *)(dev->mem + p),count))
	{
		ret = -EFAULT;
	}
	else
	{
		*ppos += count;
		ret =count;
		printk("buf=%s/n",buf);
		printk(KERN_INFO"read %d bytes from %ld/n",count ,p);
	}
	up(&dev->sem);
	return ret;
}

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(down_interruptible(&dev->sem))
		return -ERESTARTSYS;
	if(copy_from_user(dev->mem + p,buf,count))
		ret= -EFAULT;
	else
	{
		*ppos += count;
		ret=count;
		printk(KERN_INFO"written %d bytes from %ld/n",count ,p);
	}
	up(&dev->sem);
	return ret;
}

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;
}

static const struct file_operations globalmem_fops=
{
	.owner = THIS_MODULE,
	.llseek = globalmem_llseek,
	.read = globalmem_read,
	.write = globalmem_write,
	.ioctl = globalmem_ioctl,
	.open = globalmem_open,
	.release = globalmem_release,
};

static void globalmem_setup_cdev(struct globalmem_dev *dev,int index)
{
	int err,devno = MKDEV(globalmem_major,index);

	cdev_init(&dev->cdev,&globalmem_fops);
	dev->cdev.owner = globalmem_fops.owner;
	err=cdev_add(&dev->cdev,devno,1);
	if(err)
		printk(KERN_NOTICE"Error %d adding LED %d/n",err,index);
}

int globalmem_init(void)
{
	int result;
	dev_t devno = MKDEV(globalmem_major,0);

	if(globalmem_major)
		result=register_chrdev_region(devno,1,"globalmem");
	else
	{
		result=alloc_chrdev_region(&devno,0,1,"globalmem");
		globalmem_major= MAJOR(devno);
	}
	if(result < 0)
		return result;
	globalmem_devp=kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
	if(!globalmem_devp)
	{
		result=-ENOMEM;
		goto fail_malloc;
	}
	memset(globalmem_devp ,0,sizeof(struct globalmem_dev));
	globalmem_setup_cdev(globalmem_devp,0);
	init_MUTEX(&globalmem_devp->sem);
	return 0;
fail_malloc:
	unregister_chrdev_region(devno,1);
	return result;
}

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

MODULE_AUTHOR("Niu Tao");
MODULE_LICENSE("Dual BSD/GPL");

module_param(globalmem_major,int,S_IRUGO);

module_init(globalmem_init);
module_exit(globalmem_exit);
[编辑]运行
  • 编译:

Makefile:

obj-m :=globalmem.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)

all:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules 
clean:
	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions Module.symvers .Makefile.swp
  • 运行:
niutao@niutao-desktop:~/kernel/modules/globalmem$make
niutao@niutao-desktop:~/kernel/modules/globalmem$ sudo insmod globalmem.ko
[sudo] password for niutao: 
niutao@niutao-desktop:~/kernel/modules/globalmem$ sudo mknod /dev/globalmem c 252 0
niutao@niutao-desktop:~/kernel/modules/globalmem$ sudo chmod a+rw /dev/globalmem
niutao@niutao-desktop:~/kernel/modules/globalmem$ echo "hello world" >/dev/globalmem 
niutao@niutao-desktop:~/kernel/modules/globalmem$ cat /dev/globalmem 
hello world
niutao@niutao-desktop:~/kernel/modules/globalmem$

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值