您的位置:首页 > 其它

创建一个字符设备驱动

2015-06-19 18:25 281 查看
[align=left]在前面的devfs文件系统的实验中,是使用register_chrdev函数注册的字符设备驱动。本章节通过cdev_init及cdev_add函数注册一个字符设备驱动,同时通过put_user和get_user函数实现内核和应用程序之间的简单数据交互。[/align]
[align=left]在kernel/drivers/char/x4412目录下新建x4412-cdev.c文件,编辑内容如下:[/align]

#include <linux/module.h>

#include <linux/fs.h>

#include <linux/errno.h>

#include <linux/init.h>

#include <linux/cdev.h>

#include <asm/uaccess.h>

#include <linux/device.h>

static int x4412_cdev_major;

static struct cdev x4412_cdev;

static struct class *cdev_class;

int x4412_cdev_open(struct inode *inode, struct file *filp)

{

         return 0;

}

int x4412_cdev_release(struct inode *inode, struct file *filp)

{

         return 0;

}

static long x4412_cdev_ioctl(struct file *file,unsigned int cmd, unsigned long arg)

{

         return 0;

}

static ssize_t x4412_cdev_read(struct file *filp, char __user *buf,size_t size,loff_t *ppos)

{

         int read_sizes = 0;

         char *p;

         char Message[]="This is x4412/ibox devboard.";

         p = Message;

         while(size && *p)

         {

                   if(put_user(*(p++),buf++))

                            return -EINVAL;

                   size--;

                   read_sizes++;

         }

         return read_sizes;

}

static ssize_t x4412_cdev_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)

{

         int i;

         char str;

         for(i=0;i<size;i++)

         {

                   if(!get_user(str,buf++))

                            printk("%c",str);

         }

         printk("\r\n");

         return size;

}

static const struct file_operations x4412_fops =

{

         .owner = THIS_MODULE,

         .read = x4412_cdev_read,

         .write = x4412_cdev_write,

         .unlocked_ioctl = x4412_cdev_ioctl,

         .open = x4412_cdev_open,

         .release = x4412_cdev_release,

};

static void x4412_setup_cdev(void)

{

         int err, devno = MKDEV(x4412_cdev_major, 0);

         cdev_init(&x4412_cdev, &x4412_fops);

         err = cdev_add(&x4412_cdev, devno, 1);

         if (err)

                   printk(KERN_NOTICE "Error %d adding x4412_cdev", err);

}

static void x4412_clear_cdev(void)

{

         cdev_del(&x4412_cdev);

         unregister_chrdev_region(MKDEV(x4412_cdev_major, 0), 1);

}

int x4412_cdev_init(void)

{

         int result;

         dev_t devno;

         result = alloc_chrdev_region(&devno, 0, 1, "x4412-cdev");

         x4412_cdev_major = MAJOR(devno);

         if(result < 0)

         {

                   printk("register x4412-cdev error!\r\n");

                   return result;

         }

         printk("x4412-cdev main dev number:%d\r\n",x4412_cdev_major);

         x4412_setup_cdev();

         cdev_class = class_create(THIS_MODULE,"x4412-cdev");

         if(IS_ERR(cdev_class))

         {

                   x4412_clear_cdev();

                   return PTR_ERR(cdev_class);

         }

         device_create(cdev_class,NULL,MKDEV(x4412_cdev_major,0),NULL,"x4412-cdev");

         return 0;

}

void x4412_cdev_exit(void)

{

         device_destroy(cdev_class,MKDEV(x4412_cdev_major,0));

         class_destroy(cdev_class);

         x4412_clear_cdev();

}

MODULE_AUTHOR("www.9tripod.com");

MODULE_LICENSE("GPL");

module_init(x4412_cdev_init);

module_exit(x4412_cdev_exit);

复制代码
[align=left]       在模块初始化函数x4412_cdev_init中,通过alloc_chrdev_region函数自动分配设备号,再通过MAJOR函数将主设备号保存到全局变量x4412_cdev_major中,供其他函数调用。紧接着在x4412_setup_cdev函数中,通过MKDEV函数获取设备号,cdev_init和cdev_add函数实现file_operations相关函数的绑定和字符设备驱动的注册。class_create和device_create函数用于创建设备节点。[/align]
[align=left]       驱动的框架搭建完成后,开始给file_operations成员封装必要的函数。本实验需要通过get_user和put_user函数实现内核和应用程序的数据交互,可以通过read和write函数实现。read函数对应x4412_cdev_read,函数中定义了一个字符串数组Message,同时定义了一个指针指向该字符串,然后通过put_user函数将这个字符串传送给上层应用。write函数对应x4412_cdev_write,函数中定义了一个字符变量str,通过get_user函数从上层应用读取一串字符并打印出来。file_operations结构体的其他几个成员都直接返回0,不做任何操作。[/align]
[align=left]       在模块卸载函数中,device_destroy和class_destroy函数用于卸载设备节点和/sysfs目录下生成的相关目录,cdev_del函数用于注销设备驱动,unregister_chrdev_region函数用于注销设备号。注意,这几个函数在卸载函数中的先后顺序千万不要颠倒,否则将会崩溃。[/align]
[align=left]       在kernel/drivers/char/x4412/Kconfig中添加如下语句:[/align]

config X4412_CDEV_DRIVER

         tristate "x4412 cdev driver"

         default m

         help

         compile for x4412 cdev driver,y for kernel,m for module.

复制代码
[align=left]       在kernel/drivers/char/x4412/Makefile中添加如下语句:[/align]

obj-$(CONFIG_X4412_CDEV_DRIVER) += x4412-cdev.o

复制代码
[align=left]       编译内核,在kernel/drivers/char/x4412目录下将会生成驱动模块x4412-cdev.ko文件。[/align]
[align=left]       在ubuntu的用户目录或samba目录下新建应用程序x4412-cdev-app.c,编辑内容如下:[/align]

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

#define DEVICE_NAME  "/dev/x4412-cdev"

int main(int argc,char **argv)

{

       int fd;

            char buf[30];

            char str[]="hello,x4412!";

        fd = open(DEVICE_NAME,O_RDWR);

        if(fd == -1)

        {

                  printf("open device %s error \n",DEVICE_NAME);

        }

        else

        {

                   read(fd,buf,30);

                   printf("read from kernel: %s\r\n",buf);

                   usleep(5000);

                   printf("write value to kernel:\r\n");

                   usleep(5000);

                   write(fd,str,12);

                   close(fd);

        }

        return 0;

}

复制代码
[align=left]       应用程序通过open函数打开驱动后,调用read函数从驱动中读取数据并打印出来,然后将字符串数组str的内容通过write函数写到驱动中,在驱动中将应用写入的字符串打印出来。[/align]
[align=left]       执行如下指令编译应用程序:[/align]

arm-none-linux-gnueabi-gcc x4412-cdev-app.c -o x4412-cdev-app

复制代码
[align=left]       将映像文件x4412-cdev.ko及x4412-cdev-app拷贝到开发板,加载驱动测试:[/align]

[root@x4412 mnt]# cd /sys/devices/virtual/

[root@x4412 virtual]# ls

android_usb/ input/       net/         sound/       vc/

bdi/         mali/        ppp/         switch/      video4linux/

block/       mem/         rc/          tty/         vtconsole/

graphics/    misc/        regulator/   ump/

[root@x4412 virtual]# insmod /mnt/x4412-cdev.ko

[ 5602.304684] x4412-cdev main dev number:249

[root@x4412 virtual]# ls

android_usb/ input/       net/         sound/       vc/

bdi/         mali/        ppp/         switch/      video4linux/

block/       mem/         rc/          tty/         vtconsole/

graphics/    misc/        regulator/   ump/         x4412-cdev/

[root@x4412 virtual]#

复制代码
[align=left]       可以看出,加载驱动后,在/sys/devices/virtual目录下生成了x4412-cdev目录。[/align]

[root@x4412 x4412-cdev]# more /proc/devices |grep x4412

249 x4412-cdev

[root@x4412 x4412-cdev]#

复制代码
[align=left]       这里表明设备驱动的主设备号为249。[/align]

[root@x4412 x4412-cdev]# ls /dev/x4412-cdev -la

crw-rw----    1 root     root      249,   0 Oct  3 09:47 /dev/x4412-cdev

[root@x4412 x4412-cdev]#

复制代码
[align=left]       查看/dev下的设备节点时,有可能并不存在,执行mdev –s指令将会触发节点的生成。前面表明设备驱动的主设备号为249,次设备号为0。[/align]
[align=left]       执行应用程序,观察现象是否和我们期望的一致:[/align]

[root@x4412 mnt]# ./x4412-cdev-app

read from kernel: This is x4412/ibox devboard.

write value to kernel:

[ 5858.796557] hello,x4412!

[root@x4412 mnt]#

复制代码
       很明显,无论是应用程序还是驱动程序,都是按照我们的要求有条不紊的执行。再来分析cdev_init,cdev_add及register_chrdev函数的区别。事实上,使用register_chrdev函数要居多,因为它用起来更加方便。事实上,register_chrdev函数的本质就是将cdev_alloc和cdev_add函数包装起来了,而cdev_alloc函数就相当于cdev_init函数,在kernel/fs/char_dev.c中可以看到register_chrdev的函数原型__register_chrdev():

int __register_chrdev(unsigned int major, unsigned int baseminor,

                         unsigned int count, const char *name,

                         const struct file_operations *fops)

{

         struct char_device_struct *cd;

         struct cdev *cdev;

         int err = -ENOMEM;

         cd = __register_chrdev_region(major, baseminor, count, name);

         if (IS_ERR(cd))

                   return PTR_ERR(cd);

        

         cdev = cdev_alloc();

         if (!cdev)

                   goto out2;

         cdev->owner = fops->owner;

         cdev->ops = fops;

         kobject_set_name(&cdev->kobj, "%s", name);

                  

         err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

         if (err)

                   goto out;

         cd->cdev = cdev;

         return major ? 0 : cd->major;

out:

         kobject_put(&cdev->kobj);

out2:

         kfree(__unregister_chrdev_region(cd->major, baseminor, count));

         return err;

}

复制代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: