装载自:点击打开链接
1.cdev 结构体
Linux2.6内核使用 cdev 结构体描述字符设备
struct cdev // cdev 的定义在 <linux/cdev.h>
{
struct kobject kobj; //内嵌的kobject
struct module *owner; //所属模块
const struct file_operations *ops;
//文件操作结构,在写驱动时,其结构体内的大部分函数要被实现
struct list_head list;
dev_t dev; //设备号,int 类型,高12位为主设备号,低20位为次设备号
unsigned int count;
};
其中 dev_t 定义了设备号为32位,前12位为主设备号,后20位为次设备号.可以使用如下宏调用来获得主、次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
MKDEV(int major,int minor) //通过主次设备号来生成dev_t
以上宏调用在内核源码中如此定义:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
//(1<<20 -1) 此操作后,MINORMASK宏的低20位为1,高12位为0
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
下面一组函数用来对cdev结构体进行操作:
void cdev_init(struct cdev *, const struct file_operations *);//初始化,建立cdev和file_operation 之间的连接
struct cdev *cdev_alloc(void); //动态申请一个cdev内存
void cdev_put(struct cdev *p); //释放
int cdev_add(struct cdev *, dev_t, unsigned); //注册设备,通常发生在驱动模块的加载函数中
void cdev_del(struct cdev *);//注销设备,通常发生在驱动模块的卸载函数中
cdev_init()的源代码清单如下
// linux/fs/char_dev.c
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
cdev_alloc()用于动态申请一个cdev内存,代码清单如下
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
cdev_add 和 cdev_del 分别用于添加和删除一个 cdev,完成字符设备的注册和注销。
在字符设备模块加载函数中调用 cdev_add,模块写在函数中调用 cdev_del
2. 分配和释放设备号
在注册时应该先调用:
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)函数代替,
他们之间的区别在于:register_chrdev_region()用于已知设备号时,另一个用于动态申请,其优点在于不会造成设备号重复的冲突。
在注销之后,应调用:void unregister_chrdev_region(dev_t from,unsigned count)函数释放原先申请的设备号。
他们之间的顺序关系如下:
register_chrdev_region()-->cdev_add() //加载模块中
cdev_del()-->unregister_chrdev_region() //卸载模块中
3. file_operations 结构体
主要成员有open, close , llseek, read, write, readdir, ioctl, mmap,poll等,结构比较庞大,他们最终会被调用
4 Linux字符设备驱动的组成
4.1 字符设备驱动模块加载与卸载
模板如下
//xxdevice struct
struct xxx_dev
{
struct cdev cdev;
…
}xxx_dev;
static int __init xxx_init(void)
{
…
cdev_init( &xxx_dev.cdev, &xx_fops);//init the cdev ,and add file operations
register_chrdev_region or alloc_chrdev_region //get the device number
ret = cdev_add( )// register the device
…
}
static void __exit xxx_exit(void)
{
…
Unrgister the device number use unregister_chrdev_region()
…
cdev_del()
…
}
4.2 完成file_operations中的成员函数
按照file_operations中定义的统一结构编写实现函数
注意 copy_from_user 和 copy_to_user 的使用
struct file_operations xxx_fops =//init the fops
{
.owner = THIS_MODULE,
.read = xxx_read,
.open = xxx_open,
…
};
具体实例 :
- /*
- ch6 最简单的字符设备驱动
- 未加并发控制
- */
- #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
- #define MEM_CLEAR 0x1
- #define GLOBALMEM_MAJOR 260
- static int globalmem_major = GLOBALMEM_MAJOR;
- 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;
- }
- static int globalmem_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 int 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)
- return 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 bytes(s) from %d/n", count, p);
- }
- return ret;
- }
- static int 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)
- return GLOBALMEM_SIZE - p;
- if(copy_from_user(dev->mem + p, buf, count))
- ret = - EFAULT;
- else
- {
- *ppos += count;
- ret = count;
- printk(KERN_INFO"write %d bytes(s) from %d/n", count, p);
- }
- 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 = THIS_MODULE;
- dev->cdev.ops = &globalmem_fops;
- err = cdev_add(&dev->cdev, devno, 1);
- if(err)
- printk(KERN_NOTICE"Error %d adding LED%d", err, index);
- }
- int __init 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);
- return 0;
- fail_malloc: unregister_chrdev_region(devno, 1);
- return result;
- }
- void __exit globalmem_exit(void)
- {
- cdev_del(&globalmem_devp->cdev);//logout the cdev
- kfree(globalmem_devp);
- unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);//release the device number
- }
- MODULE_AUTHOR("tiany");
- 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
- KERNELDIR := /lib/modules/2.6.25-14.fc9.i686/build
- PWD := $(shell pwd)
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- modules_install:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
运行 insmod globalmem.ko ,加载模块
lsmod 和 /proc/devices 中可以看见 globalmem
通过: mknod /dev/globalmem c $device_number 0 创建设备节点
在 /sysy/module 中会有相应的目录和文件