IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Linux内核学习(3)—linux字符设备驱动程序

    cjhust发表于 2016-03-10 16:30:43
    love 0

    1、知识百科

    Linux中I/O设备分为两类:块设备和字符设备。两种设备本身没有严格限制,但是基于不同的功能进行了分类。

    (1)字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,调制解调器是典型的字符设备。

    (2)块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。

    两种设备本身并没用严格的区分,主要是字符设备和块设备驱动程序提供的访问接口是不一样的。

    2、用户需求

    今天一同事说他弟弟的考试作业是写一个字符驱动程序,有以下要求:

    (1)模块加载成功后,使用cat /proc/devices可以看到设备test,并可以看到它的主设备号;

    (2)mknod创建设备文件:mknod /dev/testdev c major minor,可以通过ls –hl /dev/testdev查看;

    (3)测试程序可以对设备进行读写;

    3、模块编写

    driver.c

    #include <linux/module.h>  

    #include <linux/init.h>  

    #include <linux/fs.h>      

    #include <linux/cdev.h>    

    #include <asm/uaccess.h>

     

    static int major = 0;   

    static int minor = 0; 

    static char devname[] = "cdev";

     

    struct cdevice               

    {

        int           value;              

        struct cdev   cdev;              

    };

     

    static struct cdevice   *mydev;

     

    static int cdevice_open(struct inode *inode, struct file *filp);

    static int cdevice_release(struct inode *inode, struct file *filp);

    static ssize_t cdevice_read(struct file *filp, char *buf, size_t size, loff_t *ppos);

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

     

    struct file_operations cdevice_fops = {

        owner: THIS_MODULE,               

        open: cdevice_open,

        release: cdevice_release,

        read: cdevice_read,

        write: cdevice_write,

    };

     

    static int cdevice_init(void)     

    {

        int    ret;

        dev_t  devno;

       

        devno = MKDEV(major, minor);

       

    if (major) {       

           //按指定的major号申请

                  ret = register_chrdev_region(devno, 1, devname);

    } else {

           //动态分配,并保存major号

                  ret = alloc_chrdev_region(&devno, minor, 1, devname);

                  major = MAJOR(devno);

        }

           

        if (ret < 0) {

            printk("[cdevice_init]: cdev register failed\n");                 

            return ret;

        }

       

        /*global variable*/

        mydev = kmalloc(sizeof(struct cdevice), GFP_KERNEL);

        if (mydev == NULL) {

            ret =- ENOMEM;           

        } else {

            mydev->value = 0;                     

            mydev->cdev.owner = THIS_MODULE;

           

            cdev_init(&mydev->cdev, &cdevice_fops);                    

            ret = cdev_add(&mydev->cdev, devno, 1);

              

            if (ret < 0) {

                printk("[cdevice_init]: add device failed\n");   

            }                    

        }

       

        printk("[cdevice_init]: cdev register success\n");

       

        return ret;

    }   

               

    static void cdevice_exit(void)     

    {

        dev_t devno;

               

        cdev_del(&mydev->cdev);                  

        kfree(mydev);

       

        devno = MKDEV(major, minor);                           

        unregister_chrdev_region(devno, 1);

            

        printk("[cdevice_exit]: cdev unregister success\n");

    }

     

    static int cdevice_open(struct inode *inode, struct file *filp)

    {

        struct cdevice   *dev;

     

        dev = container_of(inode->i_cdev, struct cdevice, cdev);

        filp->private_data = dev;

       

        printk("[cdevice_open]: success\n");

       

        return 0;

    }

     

    static int cdevice_release(struct inode *inode, struct file *filp)

    {

                         printk("[cdevice_release]: success\n");

                        

        return 0;

    }

     

    static ssize_t cdevice_read(struct file *filp, char *buf, size_t len, loff_t *off)

    {

        struct cdevice   *dev;

        unsigned long     count;

       

        dev = filp->private_data;

        count = sizeof(int);

     

        if (copy_to_user(buf, &dev->value, count)) {

            return -EFAULT;

        }

       

        return count;   

    }

     

    static ssize_t cdevice_write(struct file *filp, const char *buf, size_t len, loff_t *off)

    {

        struct cdevice   *dev;

        unsigned long     count;

       

        dev = filp->private_data;

        count = sizeof(int);

       

        if (copy_from_user(&dev->value, buf, count)) {

            return -EFAULT;

        }

       

        return count; 

    }

     

    module_init(cdevice_init);      

    module_exit(cdevice_exit);

          

    MODULE_LICENSE("GPL");

    Makefile

    ifneq ($(KERNELRELEASE), )

                                          obj-m := driver.o

    else

                                          KERNELDIR ?= /lib/modules/$(shell uname -r)/build

                                          PWD := $(shell pwd)

    all:

                                          $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

    clean:

                                          rm -rf *.o *.mod.c *.mod.o *.ko *.order *.symvers .*.cmd .tmp_versions

    endif

    Test.c

    #include <sys/types.h>

    #include <sys/stat.h>

    #include <stdio.h>

    #include <fcntl.h>

    #include <errno.h>

    #include <string.h>

     

    int main(void)

    {

        int  fd, num;

       

        fd = open("/dev/cdev", O_RDWR);

       

        if (fd == -1) {

            printf("%s\n", strerror(errno));

            return -1;                  

        }

       

        read(fd, &num, sizeof(int));                   

        printf("[read]: value = %d\n", num);

           

        printf("[write]: Please input a num: ");

        scanf("%d", &num);

        write(fd, &num, sizeof(int));                   

           

        read(fd, &num, sizeof(int));                  

        printf("[read]: value = %d\n", num);

     

        close(fd);                            

    }

    4、功能验证

    insmod

    image

    image

    image

    read and write

    image 

    image

    image

    image

    rmmod

    image 

    image 

    image


    5、知识扩展

    字符设备数据结构

    struct file和struct inode是设备驱动中二个最重要的数据结构。file结构代表一个打开的文件,它由内核在 open 时创建,并传递给在文件上操作的任何函数,直到最后的关闭。在文件的所有实例都关闭后, 内核释放这个数据结构;

    inode结构由内核在内部用来表示文件.inode 结构包含大量关于文件的信息其中dev_t i_rdev成员包含实际的设备编号.struct cdev *i_cdev中struct cdev 是内核的内部结构,代表字符设备。

    字符设备访问接口

    struct file_operations结构中的每个成员必须指向驱动中的函数,这些函数实现一个特别的操作,或者对于不支持的操作留置为 NULL。当指定为 NULL 指针时内核的确切的行为是每个函数不同的,该结构中主要函数如下:

    ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
            ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);

    filp 是文件指针,count 是请求的传输数据大小,buff 参数指向持有被写入数据的缓存,或者放入新数据的空缓存。最后 offp 是一个指针指向一个“long offset type”对象,它指出用户正在存取的文件位置, 返回值是一个"signed size type"。

    字符设备注册

    int register_chrdev_region(dev_t first, unsigned int count, char *name); 

    int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name) ;

     void unregister_chrdev_region(dev_t first, unsigned int count);

    允许驱动分配和释放设备编号的范围的函数。register_chrdev_region 需要知道设置的主编号,对于动态分配, 使用 alloc_chrdev_region 代替。

    container of

    来自Linux Kernel中的container_of宏定义, 用结构体成员的地址,结构体的类型,以及这个结构体成员在结构体当中声明的名字, 这三个参数来获取结构体的地址。和nginx的ngx_queue_data的功能相似。

    item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);

    #define ngx_queue_data(q, type, link)                                         \

    (type *) ((u_char *) q - offsetof(type, link))

    设备号

    每个文件都有2个设备号,第1个是主设备号,标识驱动程序,第2个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备。

    6、参考资料

    http://www.oschina.net/code/snippet_579503_22177

    http://www.2cto.com/kf/201303/198949.html

    http://blog.csdn.net/bonnshore/article/details/7860997

    http://rangercyh.blog.51cto.com/1444712/521244

    http://oss.org.cn/kernel-book/ldd3/ch01s03.html

    http://blog.csdn.net/waldmer/article/details/17611579

     



沪ICP备19023445号-2号
友情链接