kernel module编程(四):设备属性和与上层应用的联系

来源:互联网 发布:为知笔记官网 编辑:程序博客网 时间:2024/06/16 23:22

   本文也即是《Linux Device Drivers》一书第三章Char Drivers的读书笔记之二。

   这部分开始有些觉得阴涩难懂。我上网去查,没能找到这本书的Example的例子,所以决定还是靠自己。我先写一个应用层的例子,通过这个例子来触发kernel module的一些操作,这样比较容易理解。

#include <stdlib.h>
#include <stdio.h>

int main(int argc ,char * argv[])
{
        FILE * file = NULL;

        printf("************** TEST ACCESS DRIVER**************/n");
        file = fopen("/dev/scull0","w");
        if(file == NULL){
                printf("Open scull0 error!/n");
                exit(-1);
        }

        fclose(file);
        return 0;
}

  我们已经向成功请求分配了设备号码,一个内核模块,我们希望上层能够使用它的能力或者资源。有下面的重要的数据结构。

  struct file_operations,里面的参数处理strcut module外都是一些函数,这些函数和上层应用操作kernel的触发相关,内核模块可以提供不同的通信方式,例如BLOCK,NOBLOCK等等。应用某些函数的参数操作,触发内核模块对应函数的操作,例如测试效力中,fopen触发设备struct file_operations中open函数。文件操作,在上层应用的输入输出,包括socket中是经常使用的概念。例如fd:file descripor文件描述字。

  strcut file,给出设备的属性,例如是否可写等等。在用户触发时,kernel也会将该用户对设备的操作属性一起送下来。

  struct inode,在应用通过open或者其他方式,和内核模块建立连接后,他需要进一步操作,这是内核模块收到触发参数中一个重要的数据,他使得我们的内核模块可以详细对应具体使用的device。一般我们只关心dev_t i_rdev和struct cdev *i_cdev,这两个参数。

  继续在scull的例子。先解析一下scull对于内存空间的使用。如下图所示。

[wei@wei ~]$ cat scull.h
#ifndef _WEI_SCULL_H
#define _WEI_SCULL_H

#define SCULL_MAJOR          0
#define SCULL_MINOR_MIN 0
#define SCULL_DEV_NUM    4


/*这是根据内存组织情况给的链表结构*/
struct scull_qset{
        void             ** data;
        struct scull_qset * next;
};
/* 这是根据内存组织结构给的一个是Quantum的大小,一个是Data中具有多少个Quantum的队列长度*/
#define SCULL_QUANTUM 1024
#define SCULL_QSET      64

/*这是我们为内核模块对应的设备提供的结构,*/
struct scull_dev {
        struct scull_qset     * data;
        int                           quantum;/* the quantum size */
        int                           qset;        /* the array size */

        struct  cdev            cdev;         /* Char device structure */
};

static void scull_setup_cdev(struct scull_dev * dev, int index);

int scull_trim(struct scull_dev * dev);

int scull_open(struct inode * inode , struct file * file);
int scull_release(struct inode * inode , struct file * file);

#endif

  在我们自定义的device结构中,最后一个参数struct cdev是个非常重要的参数,它是char device struct。通过下面三个系统函数去初始化它,将它和某个文件操作联系起来。将它加入kernel,以及从kernel中删除。

void cdev_init(struct cdev * cdev, strcut file_operations *fops);
int  cdev_add (strcut cdev * dev, dev_t num, unsigned int count);
void cdev_del (strcut cdev * dev);

  下面是scull.c,我们分析一下如何填写和使用上述三个重要的数据结构

[wei@wei ~]$ cat scull.c
#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#include "scull.h"

MODULE_LICENSE("Dual BSD/GPL");

dev_t   dev;
int     is_get_dev = -1;


static int      scull_major = SCULL_MAJOR;

/*这是文件操作结构,我们的四个scull0-3都具备同样的属性,应此可以对应到同一个数据结构中,请注意这个写法,我们在这里列出,使得程序更已读和便于修改。这在这个例子中,我们只是简单地进行打开和关闭的操作,所以我们只提供open和release两个函数映射*/
struct file_operations scull_fops = {
        .owner          = THIS_MODULE,  /*这个参数不是一个操作,用于防止模块在操作过程中被卸载。*/
        .open           = scull_open,
        .release        = scull_release,

};

/* strcut scull_dev是我们为每个设备设定的所有信息,四个设备放置在一个数组中 */
struct scull_dev mydev[SCULL_DEV_NUM];

static int __init scull_init(void)
{
        int i =0;

        printk("Scull module init enter/n");
        if(scull_major){
                dev = MKDEV(scull_major,SCULL_MINOR_MIN);
                is_get_dev = register_chrdev_region(dev, SCULL_DEV_NUM,"scull");
        }else{
                is_get_dev = alloc_chrdev_region(&dev,SCULL_MINOR_MIN, SCULL_DEV_NUM,"scull");
                scull_major = MAJOR(dev);
        }
        if(is_get_dev < 0){
                printk(KERN_WARNING "scull: can't get device major number %d/n",scull_major);
                return is_get_dev;
        }


    /* 为这四个设备分别进行初始化 */
        for(i = 0 ; i < SCULL_DEV_NUM; i ++){
                scull_setup_cdev(&mydev[i],i);
        }
        return 0;
}


static void __exit scull_exit(void)
{
        if(is_get_dev < 0){
                return ;
        }else{
                int i = 0;

                /* 对于任何进行了cdev_add操作的设备,应该在卸载模块的时候进行cdev_del,将它从kernel中删除。*/
                for(i = 0; i < SCULL_DEV_NUM; i ++){
                        cdev_del( & mydev[i].cdev );
                }
                unregister_chrdev_region(dev,SCULL_DEV_NUM);
                printk("Scull module exit/n");
        }
}


module_init(scull_init);
module_exit(scull_exit);


/* 根据我们为设备定义的结构,进行初始化*/
static void scull_setup_cdev(struct scull_dev * dev, int index)
{
        int err;
        /* 通过第二个参数index,我们可以知道具体是那个scullx,可以对应到相应的设备上。*/
        int devno = MKDEV(scull_major,SCULL_MINOR_MIN + index);
        printk("scull%d %d,%d is %d/n", index,scull_major, SCULL_MINOR_MIN + index, devno);

        /*进行char device的初始化,和file operation相关联,设置其属性,并将通过对应设备将其加入kernel中*/
        cdev_init (& dev->cdev, & scull_fops);
        dev->cdev.owner = THIS_MODULE;
        dev->cdev.ops   = & scull_fops;

        err = cdev_add(&dev->cdev,devno,1);
        if(err){
                printk(KERN_NOTICE "scull : Err %d adding scull %d/n", err,index);
        }
}

/*当用户程序打开设备是触发,例如测试小程序中的fopen(),kernel将送来inode和该用户的文件属性,不同用户有不同的操作权限。*/
int scull_open(struct inode * inode ,struct file *filp)
{
        struct scull_dev * dev;
       
        /*这里给出了通过inode获得对应设备的方式,第一种通过获取设备号码来进行检索,第二中是个很有趣的函数contianer_of,据ldd3这本书说这是kernel hackers提供的。container_of(pointer,container_type,container_field),则是太方便了。方便得有些怀疑是否保险。*/
        printk("scull_open is called, node %d %d/n",imajor(inode), iminor(inode));
        dev = container_of(inode->i_cdev,struct scull_dev,cdev);
        /* 我们将dev作为filp,private_data进行存贮,避免我们在任何一次操作中都进行查询*/
        filp->private_data = dev;
       
        /* 查看一下用户权限。我们可以在fopen中使用"r"和"w"分别查看。如果给出的是只写操作,将清空原来的所有数据(scull_trim())。*/
        printk("scull: open mode is %x/n",filp->f_mode);
        if((filp->f_flags & O_ACCMODE) == O_WRONLY){
                printk("scull: only write: free all data/n");
                scull_trim(dev);
        }

        return 0;
}
/* 我们在用户程序fclose()时调用。*/
int scull_release(struct inode * inode, struct file * filp)
{

      printk("scull: release is called./n"); 

      return 0;
}

/* 清空所有数据 */
int scull_trim(struct scull_dev * dev)
{
        struct scull_qset * next ,*dptr;
        int qset = dev-> qset;
        int i ;

        for (dptr = dev->data; dptr != NULL; dptr = next){
                if(dptr->data){
                        for(i = 0; i < qset; i ++)
                                kfree(dptr->data[i]);
                        kfree(dptr->data);
                        dptr->data = NULL;
                }
                next = dptr->next;
                kfree(dptr);
        }
        dev->size = 0;
        dev->quantum = SCULL_QUANTUM;
        dev->qset = SCULL_QSET;
        dev->data = NULL;
        return 0;
}

相关链接:我的与kernel module有关的文章