字符设备是驱动开发中最简单的一种设备(通过字符设备来写寄存器,从而起到一个驱动的作用),struct cdev结构体是内核用来描述一个字符设备的,是内核已经封装好的,程序员只需要直接定义一个这样的结构体变量即可。
struct cdev {
struct kobject kobj; // 每个cdev 都有一个 kobject
struct module *owner; // 指向实现驱动的模块
const struct file_operations *ops; // 操纵这个字符设备文件的方法
struct list_head list; // 与cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; // 起始设备编号
unsigned int count; // 设备范围号大小
};
struct file_operations是操纵这个字符设备的一些方法
struct file_operations {
struct module *owner; //它是一个指向拥有这个结构的模块的指针
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
};
接下来在虚拟机上写一个字符设备,代码如下:
chardevice.cpp
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230 //设备号
static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major,int,S_IRUGO);
struct globalmem_dev
{
struct cdev cdev; //描述字符设备的结构体
unsigned char mem[GLOBALMEM_SIZE];
};
struct globalmem_dev* globalmem_devp;
static int globalmem_open(struct inode* inode,struct file* filp)
{
filp->private_data = globalmem_devp;
return 0;
}
static int globalmem_release(struct inode* inode,struct file* filp)
{
return 0;
}
static long globalmem_ioctl(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 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 0;
if(count> GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if(copy_to_user(buf,dev->mem + p,count) )
ret = -EFAULT;
else
{
*ppos = *ppos + count;
ret = count;
printk(KERN_INFO"read %u bytes(s) from %lu\n",count,p);
}
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 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"write %u bytes(s) from %lu\n",count,p);
}
return ret;
}
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_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;
err = cdev_add(&dev->cdev,devno,1); //向系统添加一个cdev
if(err)
{
printk(KERN_NOTICE"Error %d adding globalmem%d",err,index);
}
}
static int __init globalmem_init(void)
{
int ret;
dev_t devno = MKDEV(globalmem_major,0);
if(globalmem_major)
{
ret = register_chrdev_region(devno,1,"globalmem"); //申请设备号(知道起始设备号)
}
else
{
ret = alloc_chrdev_region(&devno,0,1,"gobalmem");//申请设备号(不知道起始设备号)
globalmem_major = MAJOR(devno);
}
if(ret < 0)
return ret;
globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);//为设备申请空间
if(!globalmem_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
globalmem_setup_cdev(globalmem_devp,0);
return 0;
fail_malloc:
unregister_chrdev_region(devno,1);
return ret;
}
module_init(globalmem_init);
static void __exit globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev); //释放cdev
kfree(globalmem_devp);
unregister_chrdev_region( MKDEV(globalmem_major,0), 1); //释放设备号
}
module_exit(globalmem_exit);
MODULE_AUTHOR("liwanchao");
MODULE_LICENSE("GPL");
Makefile如下:
ifneq ($(KERNELRELEASE),)
param-objs:= chardevice.o
obj-m := chardevice.o
else
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif
如何把这个模块加载到内核呢?
Makefile写好了之后,直接make,这时候可以看到生成了chardevice.ko文件。
运用insmod chardevice.ko,就加载进内核了。(root用户)
这个时候vim /proc/devices就可以看到刚才那个设备的设备号和设备名字:(root用户)
设备号为230,名字为globalmem
然后运用卸载工具rmmod chardevice 就卸载了这个设备。(root)