做linux内核驱动,最重要的一种设备驱动就是字符设备驱动,也是最基本的最需要彻底掌握的。
字符设备几个重要的结构体和接口函数:
字符设备结构体
struct cdev
初始化字符设备
cdev_init(struct cdev*,struct file_operations*);
注册或者说添加一个字符设备到内核
cdev_add(struct cdev*,int,int);
删除某个字符设备
cdev_del(struct cdev*);
我们可以自己定义一个字符设备结构体,在这个结构体中包含struct cdev。事实上很多其他的驱动设备,也是这样做的,比方i2c设备、usb设备等。
第一步、
初始化cdev_init这个cdev,这样就可以将cdev和file_operations结构体绑定在一起,用户空间才能够通过read、write等函数来操作这个cdev设备。
第二步、
注册这个cdev到内核中,使用cdev_add接口。这样一个简易的cdev字符算是存在于内核中了。
卸载这人cdev可以通过cdev_del接口函数来做,一般在__exit函数中调用。
这里来实现一个简单的秒字符设备。
1. second_dev.c
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.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>
#include <asm/io.h>
#include <linux/uaccess.h>
#define SECOND_MAJOR 255
static struct class *firstdrv_class;//auto create a device node need a class
static int second_major = SECOND_MAJOR;// MAJOR 255
//struct second device, contians struct cdev
struct second_dev {
struct cdev cdev;
atomic_t counter;//define atomic_t counter
struct timer_list s_timer;//define the timer handle function, timeout etc.
};
struct second_dev *second_devp;//a pointer of struct device
//timer handle func
static void second_timer_handle(unsigned long arg)
{
unsigned long current_jiffies = jiffies;
mod_timer(&second_devp->s_timer,jiffies + HZ);//reload timer the timeout plus 1s
atomic_inc(&second_devp->counter);//counter plus 1
printk("current jiffies is %ld\n",current_jiffies);//
}
int second_open(struct inode *inode,struct file *filp)//open
{
init_timer(&second_devp->s_timer);// init the timer
second_devp->s_timer.function = &second_timer_handle;//specific the timer handle function
second_devp->s_timer.expires = jiffies + HZ;//specific the expires jiffies + HZ
add_timer(&second_devp->s_timer);//add the timer to timer_list
atomic_set(&second_devp->counter,0);//init the counter to 0
return 0;
}
//close file
int second_release(struct inode *inode,struct file *filp)
{
del_timer(&second_devp->s_timer);//delete the timer
return 0;
}
static ssize_t second_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
int counter;
counter = atomic_read(&second_devp->counter);//get the counter
if(put_user(counter,(int *)buf))//put_user() kernel -> userspace
return -EFAULT;
else
return sizeof(unsigned int);
}
static ssize_t second_write(struct file* filp,const char __user * buf, size_t count, loff_t* ppos)
{
char* value = (char*)kmalloc(count,GFP_KERNEL);
unsigned long size = copy_from_user((void*)value,buf,count);
printk("xujiwei---> copy form user, value = %s\n",value);
kfree(value);
return size;
}
static const struct file_operations second_fops = {
.owner = THIS_MODULE,
.open = second_open,
.release = second_release,
.read = second_read,
.write = second_write,
};
//init cdev
static void second_setup_cdev(struct second_dev *dev,int index)
{
int err,devno = MKDEV(second_major,index);//merge the major and minor device index
cdev_init(&dev->cdev,&second_fops);//init the cdev and connect the cdev and file_operations
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev,devno,1);//add the cdev
if(err)
printk(KERN_NOTICE "Error %d adding LED%d",err,index);
}
int __init second_init(void)
{
int ret;
dev_t devno = MKDEV(second_major,0);
if(second_major)
ret = register_chrdev_region(devno,1,"second");
else{
ret = alloc_chrdev_region(&devno, 0, 1, "second");
second_major = MAJOR(devno);
}
if(ret < 0)
return ret;
firstdrv_class = class_create(THIS_MODULE, "second_major");//create the firstdrv class --> /sys/class/second_major
device_create(firstdrv_class,NULL,devno,NULL,"second");//auto create device node /dev/LED
second_devp = kmalloc(sizeof(struct second_dev),GFP_KERNEL);
if(!second_devp){
ret = -ENOMEM;
goto fail_malloc;
}
memset(second_devp,0,sizeof(struct second_dev));
second_setup_cdev(second_devp,0);
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
void __exit second_exit(void)
{
cdev_del(&second_devp->cdev);//delete the cdev
kfree(second_devp);
unregister_chrdev_region(MKDEV(second_major,0),1);
device_destroy(firstdrv_class,MKDEV(second_major,0));
class_destroy(firstdrv_class);
}
MODULE_LICENSE("GPL");
module_init(second_init);
module_exit(second_exit);
2. Makefile
#
# Made from Makefile.org by xujiweigo@163.com
#
obj-m += second-device.o
second-device-y := second-dev.o
3. make.sh
#/bin/sh
ver=`uname -r`
CONFIG_USBIP_CORE=m \
CONFIG_USBIP_VHCI_HCD=m \
CONFIG_USBIP_HOST=m \
make -C /lib/modules/$ver/build M=$PWD "$@"
直接运行make.sh就这可编译出一个ko文件second-device.ko,然后insmod到内核中,这样一个秒字符设备就做好了。
编写一个测试demo,看看效果如何。
second-dev-test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
int counter = 0;
int old_counter = 0;
char buf[20] = "xujiwei";
fd = open("/dev/second",O_RDWR);//open device
if(fd != -1){
printf("write size = %ld\n",write(fd,buf,sizeof("xujiwei")));
while(1){
read(fd,&counter,sizeof(unsigned int));
if(counter != old_counter){
printf("seconds after open /dev/second : %d\n",counter);
old_counter = counter;
}
}
}
else{
printf("Device open failure\n");
}
}
编译然后运行一下。
成功。