本文也即是《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 );
。 */
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