,本文假定读者具备一定linux基础以及对linux驱动基础有所了解。
➜ ~ cat /proc/version
Linux version 3.13.0-43-generic (buildd@akateko) (gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) )
#72-Ubuntu SMP Mon Dec 8 19:35:44 UTC 2014
globalmem.c
关键词:register_chrdev_region()、alloc_chrdev_region()、unregister_chrdev_region()、cdev_init()、cdev_add()、cdev_del()
#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 <linux/slab.h>
MODULE_LICENSE("Dual BSD/GPL");
#define GLOBALMEM_SIZE 0X1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 240
static int 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;
printk(KERN_ALERT "globalmem open\n");
return 0;
}
//文件释放函数
int globalmem_release(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "globalmem release\n");
return 0;
}
//ioctl设备控制函数,linux kernel高版本中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;
}*/
//读函数
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
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 %d byte(s) from %d", 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 "written %d byte(s) form %d\n", count, p);
}
return ret;
}
//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;
}
//文件操作结构体
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
// .ioctl = globalmem_ioctl,//Linux kernel高版本中ioctl已被两个XX_ioctl替代,参见下面注释
.open = globalmem_open,
.release = globalmem_release,
};
//初始化并注册cdev
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 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,"globalmem");
else//动态申请设备号
{
result = alloc_chrdev_region(&devno,0,1,"globalmem");
globalmem_major = MAJOR(devno);
}
if(result < 0)
return result;
printk(KERN_ALERT "T1\n");
//动态申请设备结构体的内存
globalmem_devp = (struct globalmem_dev *)kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
printk(KERN_ALERT "T0\n");
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 globalmem_exit(void)
{
cdev_del(&globalmem_devp->cdev);
kfree(globalmem_devp);
unregister_chrdev_region(MKDEV(globalmem_major,0),1);
}
module_param(globalmem_major, int,S_IRUGO);
module_init(globalmem_init);
module_exit(globalmem_exit);
下面是Linux kernel 3.14的源码中的file_operations结构体(include/linux/fs.h),由于有时候内核版本的升级会给接口带来一定的变化,所以在写驱动时,应该同时具有该版本的源码,以编写出对应内核接口的函数。所以对于上面的ioctl函数,自行参考修改一下即可编译通过。
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 (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
......
}
Makefile
ifneq ($(KERNELRELEASE),)
obj-m := globalmem.o
else
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules
endif
clean:
rm -f *.o *.ko *.mod.c .globalmem*
用户态程序test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
fd = open("/dev/globalmem", O_RDWR);
if(fd < 0)
{
perror("open");
}
return EXIT_SUCCESS;
}
运行测试程序
root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem#insmod globalmem.ko
root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem# cat /proc/devices | grep "globalmem"
240 globalmem
root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem# mknod /dev/globalmem c 240 0
root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem# ll /dev/globalmem
crw-r--r-- 1 root root 240, 0 12月 2 19:47 /dev/globalmem
root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem#./test
root@selfimpr-pc:/home/selfimpr/wenqian/lkp/globalmem#dmesg
[210299.896657] T1
[210299.896665] T0
[210393.405124] globalmem open
[210393.405201] globalmem release
上面一个简单的case,只是大致的概述下linux字符设备驱动的编写流程,但这也是字符驱动的一个最初雏形,类似于单片机的最小系统之类。
在linux内核中,使用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;
};
关于设备号请具体参考任何一本linux字符驱动书籍。
cdev结构体中一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数(file_operations请参考前面的博文文件系统系列)。而下层的具体实现,则根据用户及驱动编写。
linux内核提供了一组函数用于操作cdev结构体:
void cdev_init(struct cdev *, struct file_operations *);//初始化cdev成员
struct cdev *cdev_alloc(void);//动态申请一个cdev内存
int cdev_add(struct cdev *, dev_t, unsigned);//向系统添加一个cdev
void cdev_del(struct cdev *);//向系统删除一个cdev
分配和释放设备号
在调用cdev_add()函数向系统注册字符设备之前,应首先调用 register_chrdev_region() 或 alloc_chrdev_region()函数向系统申请设备号
int register_chrdev_region(dev_t first, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned firstminor, unsigned int count, const char *name);
其中register_chrdev_region函数用于已知起始设备的设备号的情况,而alloc_chrdev_region函数用于设备号未知,向系统动态申请未被占用的设备号的情况,函数调用成功以后,会把得到的设备放入第一个参数dev中。
相反的,在调用cdev_del函数从系统注销字符设备之后,unregiter_chrdev_region()应该用以释放原先申请的设备号:
void unregister_chrdev_region(dev_t first, unsigned count);
我们可以使用下列宏从dev_t获得主设备号和次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
使用下列宏(也是我们通常使用的)则可以通过主设备号和次设备号生成dev_t
MKDEV(int major, int minor)