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

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

时间:2023-05-08 18:32:27浏览次数:36  
标签:kernel SCULL struct int 编程 dev cdev module scull


   本文也即是《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对于内存空间的使用。如下图所示。

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

[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 ); 
        。 */ 
         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,SCULL,struct,int,编程,dev,cdev,module,scull
From: https://blog.51cto.com/u_9877302/6255399

相关文章

  • kernel module编程(三):获取(分配或注册)设备号
    《LinuxDeviceDrivers》一书第三章CharDrivers的读书笔记之一。我们在/dev中可以查看设备节点,每个设备有一个主号码(major)以及一个副号码(minor),通常一个major号码对应某一种设备,虽然linux允许多种设备共享一个major号码。minor号码用于kernel具体进行设备的对应,kernel并不了解......
  • ThreadLocal让你的多线程编程更简单【Java多线程必备】
    一、介绍ThreadLocal是Java中的一个线程局部变量,该变量在多线程并发执行时,为每个线程都提供了一个独立的副本。简单来说,ThreadLocal提供了一种在多线程环境中,使每个线程绑定自己独立的变量的方法,每个线程可以独立地改变自己的副本,而不会影响其他线程所对应的副本。二、特性1.......
  • Javascript异步编程的4种方法
    你可能知道,Javascript语言的执行环境是"单线程"(singlethread)。所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须......
  • 【编程入门】应用市场(NodeJS版)
    背景前面已输出多个系列:《十余种编程语言做个计算器》《十余种编程语言写2048小游戏》《17种编程语言+10种排序算法》《十余种编程语言写博客系统》《十余种编程语言写云笔记》《N种编程语言做个记事本》目标为编程初学者打造入门学习项目,使用各种主流编程语言来实现。让想学......
  • MFC-GetModuleHandle获取动态链接库的模块句柄
     HMODULEhmodule=::GetModuleHandle(_T("kernel32.dll"));//获取动态链接库的模块句柄/*参数:LPCTSTR指向含有模块名称字符串的指针返回值:HMODULE,如执行成功成功,则返回模块句柄。零表示失败。获取错误信息*/     ......
  • c#中使用 async 和 await 的异步编程
    什么是异步编程异步编程是对线程的一种应用方式。类似于人跑步时戴着耳机听歌,这两个行为可以同时进行,而不是先跑完步再听歌。异步编程就是同一时间做多件事,通常异步编程就是在继续运行原有逻辑的同时,把耗时的操作放进一个单独的线程中进行并行处理,以重复利用CPU资源以及节省总的......
  • 网络编程
    一.楔子你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运行,你就会发现,这两个python的文件分别运行的很好。但是如果这两个程序之间想要传递一个数据,你要怎么做呢?这个问题以你现在的知识就可以解决了,我们可以创建一个文件,把a.py想要传递的内容写到文件中......
  • 面向对象编程
    对象的概念”面向对象“的核心是“对象”二字,而对象的精髓在于“整合“,什么意思?所有的程序都是由”数据”与“功能“组成,因而编写程序的本质就是定义出一系列的数据,然后定义出一系列的功能来对数据进行操作。在学习”对象“之前,程序中的数据与功能是分离开的,如下#数据:name、ag......
  • 2023.5.7编程一小时打卡
    一、问题描述:编写程序提示用户输入一个班级中的学生人数n,再依次提示用户输入n个人在课程A中的考试成绩,然后计算出平均成绩显示出来。二、解题思路:首先,定义一个vector类型的成员,通过用户输入的人数进行对vector的数据添加,最后进行加和求其平均值。 三、代码实现:1#include......
  • ERROR: All flavors must now belong to a named flavor dimension. Affected
    在ijkplayer-example的build.gradle中  添加flavorDimensions"800800",其中的800800为project:build.gradle中的versionCode=800800值一样。 3.   AGPBI:{"kind":"error","text":"error:\u0027@@array/pref_entries_player\u0027......