开始从头学起linux 设备驱动,当然是先从字符驱动看起。
下面仿照着书上的例子,写了一个misc 字符驱动。
root@jay-LJ:/home/jay/globalmem# tree globalmem/
globalmem/
├── globalmem.c
└── Makefile
首先咱来看下Makefile, 其实这东西都一个模子,
KVERS = $(shell uname -r)
obj-m += globalmem.o
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build/ M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build/ M=$(CURDIR) clean
然后是我们的驱动文件globalmem.c,我们从init函数看起
int globalmem_init(void)
{
int 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_devp->mdev = mdev_struct;
result = misc_register(&(globalmem_devp->mdev));
if(result<0)
return result;
else
return 0;
fail_malloc:
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));
看下这个指针的定义
struct globalmem_dev {
struct miscdevice mdev;
unsigned char mem[GLOBALMEM_SIZE];
};
struct globalmem_dev *globalmem_devp;
globalmem_dev结构体中内嵌了一个miscdevice结构体,这个结构体就是描述我们的misc字符驱动的结构体,若想注册一个misc 字符设备就必须包含有这样一个结构体,
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
};
这个结构体描述了这个驱动的信息,包含次设备号, 文件操作结构体,驱动名称等。
内存分配结束之后是调用misc_register来注册我们的misc驱动,使用misc字符驱动相对于标准的字符驱动写起来简单。
然后是exit函数,跟init相反:
void globalmem_exit(void)
{
misc_deregister(&(globalmem_devp->mdev));
if(globalmem_devp)
kfree(globalmem_devp);
}
module_init(globalmem_init);
module_exit(globalmem_exit);
千万别忘了最后要释放我们分配的内存。
然后使我们的miscdevice和fops结构体
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.open = globalmem_open,
.release = globalmem_release,
.unlocked_ioctl = globalmem_ioctl,
.read = globalmem_read,
.write = globalmem_write,
};
static struct miscdevice mdev_struct = {
.minor = MISC_DYNAMIC_MINOR,
.name = "globalmem",
.fops = &globalmem_fops,
};
这里主要是这个file_operations接头体的定义,这里主要是把这个结构体中我们想要实现的读,写等方法与对应的回调函数挂钩起来。
然后就是我们的读写函数了
int globalmem_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "globalmem open!\n");
filp->private_data = globalmem_devp;
return 0;
}
int globalmem_release(struct inode *inode ,struct file *filp)
{
printk(KERN_INFO "globalmem release!\n");
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; //get global data pointer
switch(cmd) {
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "clear globalmem!\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; //get global data pointer
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(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; //get global data pointer
if(p >= GLOBALMEM_SIZE)
return 0;
if(count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if(copy_from_user(dev->mem+p, buf, count)) {
printk(KERN_INFO "copy from user error!!\n");
ret = -EFAULT;
}
else {
*ppos += count;
ret = count;
printk(KERN_INFO "written %u bytes(s) from %lu\n",count,p);
}
return ret;
}
我这里实现了open release write read四个方法,open方法很简单,把我们的自己定义的全局的结构体指针保存到我们驱动中的私有指针,这样的话,在我们别的方法中就可以直接用这个私有指针来获取我们的全局结构体指针,
filp->private_data = globalmem_devp;
保存指针,
struct globalmem_dev *dev = filp->private_data; //get global data p
获取指针。
在我们的read/write中是与用户空间进行数据处理的过程,主要是调用了copy_from_user和copy_to_user这2个函数实现的,实现了从用户态到内核态的数据传递。
在应用层使用read/write来进行系统调用call到我们这边的read/write回调函数。
============================================================
接下来我们来测试一下我们的驱动,
编译
root@jay-LJ:/home/jay/globalmem/globalmem# make
make -C /lib/modules/2.6.35-22-generic/build/ M=/home/jay/globalmem/globalmem modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.35-22-generic'
CC [M] /home/jay/globalmem/globalmem/globalmem.o
/home/jay/globalmem/globalmem/globalmem.c:110: warning: initialization from incompatible pointer type
Building modules, stage 2.
MODPOST 1 modules
CC /home/jay/globalmem/globalmem/globalmem.mod.o
LD [M] /home/jay/globalmem/globalmem/globalmem.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.35-22-generic'
加载
insmod globalmem.ko
查看驱动
root@jay-LJ:/home/jay/globalmem/globalmem# ls -l /dev/globalmem
crw------- 1 root root 10, 53 2012-03-27 21:11 /dev/globalmem
进行读写测试
root@jay-LJ:/dev# chmod 666 globalmem
root@jay-LJ:/dev# echo "Hello globalmem driver" > globalmem
root@jay-LJ:/dev# cat globalmem
Hello globalmem driver
root@jay-LJ:/dev#
当然了,我们也可以自己写一个测试的应用程序来测试我们的驱动。
结束。
=========================================================
mail & MSN :zhangjie201412@live.com
=========================================================