栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux字符设备驱动学习(1)——scull模块与驱动相关概念

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux字符设备驱动学习(1)——scull模块与驱动相关概念

一、scull介绍

1.scull(Simple Character Utility for loading Localities),即区域装载的简单字符工具。scull是一个操作内存区域的字符设备驱动程序,这片区域相当于一个设备。

2.优点
scull和硬件无关,而只是操作从内核中分配的一些内存,任何人都可以编译和运行scull,并且可以将scull移植到Linux支持的计算机平台上。

3.工作过程
①Linux启动时运行加载驱动模块的脚本。insmod时调用模块的init函数进行初始化,调用cdev_init( )和cdev_add( )来进行字符设备的初始化,并把这个设备添加进入系统。这个过程会创建/proc/modules、/proc/devices两个文件和/sys/devices目录中的对应项目。

②用户空间的程序通过系统调用open打开设备(eg:fopen("/dev/scull0",“w”)),此时Linux会生成一个file结构,其中包含f_pos(位置指针),f_mode(打开方式是否只读等)等状态信息。然后调用模块中定义的open( )函数,把刚生成的file结构作为参数传给open( )。open( )通常需要根据情况做一些诸如设置互质标记位之类的工具。

③用户空间的程序通过系统调用进行读写操作(eg:使用fprintf( )等函数),会调用驱动中的read( ),write( ),llseek( )等函数。

④此时若有另一个用户程序打开这个设备文件,则会再创建一个file结构。

⑤每一个程序完成操作,关闭设备文件时,会销毁对应的file结构。但是,只有最后一个进程关闭文件时才会调用驱动程序的release( )函数。

⑥关机时调用驱动模块的exit函数,释放资源。

二、设备与驱动工作关联


1.Linux内核中
①使用cdev结构体来描述字符设备
②通过cdev的成员dev_t来定义设备号(主、次设备号)来确定字符设备的唯一性
③通过cdev的成员file_operations来定义字符设备驱动提供的VFS的函数接口
如open( ),read(),write( )等。

2.Linux字符设备驱动中
①模块加载函数通过register_chrdev_region( )或alloc_chrdev_region( )来静态或动态获取设备号。
②通过cdev_init( )建立cdev与file_opreations之间的联系,通过cdev_add( )向系统添加一个cdev以完成注册。
③模块卸载通过cdev_del( )来注销cdev,通过unregister_chrdev_region( )来释放设备号。

3.用户空间访问该设备程序
通过Linux系统调用,如open( ),read( ),write( ),来调用file_opreations来定义字符设备提供给VFS的接口函数。

三、函数源代码介绍

1.cdev结构体(用来描述一个字符设备)


 
struct cdev { 
	struct kobject kobj;                  //内嵌的内核对象.
	struct module *owner;                 //该字符设备所在的内核模块的对象指针.
	const struct file_operations *ops;    //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.
	struct list_head list;                //用来将已经向内核注册的所有字符设备形成链表.
	dev_t dev;                            //字符设备的设备号,由主设备号和次设备号构成.
	unsigned int count;                   //隶属于同一主设备号的次设备号的个数.
};

2.cdev_init()函数
作用:建立cdev和file_opreations之间的联系。

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev); 					//将整个结构体清零初始化。
	INIT_LIST_HEAD(&cdev->list);			    	//初始化list成员使其指向自身
	kobject_init(&cdev->kobj, &ktype_cdev_default); //初始化kobj成员
	cdev->ops = fops;								//初始化ops成员
}

3.cdev_alloc( )函数
作用:该函数主要分配一个struct cdev结构,动态申请一个cdev内存,并做了cdev_init中所做的前面3步初始化工作(第四步初始化工作需要在调用cdev_alloc后,显式的做初始化即: .ops=xxx_ops).

struct cdev *cdev_alloc(void)
{
	struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
	if (p) {
		INIT_LIST_HEAD(&p->list);
		kobject_init(&p->kobj, &ktype_cdev_dynamic);
	}
	return p;
}

在上面的两个初始化的函数中,我们没有看到关于owner成员、dev成员、count成员的初始化;其实,owner成员的存在体现了驱动程序与内核模块间的亲密关系,struct module是内核对于一个模块的抽象,该成员在字符设备中可以体现该设备隶属于哪个模块,在驱动程序的编写中一般由用户显式的初始化 .owner = THIS_MODULE, 该成员可以防止设备的方法正在被使用时,设备所在模块被卸载。而dev成员和count成员则在cdev_add中才会赋上有效的值。

4.cdev_add()函数
作用:该函数向内核注册一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经可以使用了。

int cdev_add(struct cdev *p, dev_t dev, unsigned count);

5.cdev_del()函数
作用:该函数向内核注销一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经不可以使用了。

void cdev_del(struct cdev *p)

四、代码测试

#include 
#include 
#include  
#include 
#include  
#include 

MODULE_LICENSE("GPL");


int scull_major = 0;
int scull_minor = 0;
struct cdev cdev; 

#define MAX_SIZE 10
size_t size = 0;
char store[MAX_SIZE];



int scull_open(struct inode *inode , struct file *filp)
{
  	
  	if ( (filp ->f_flags & O_ACCMODE) == O_WRONLY) {
    		size = 0;
  	}
  	return 0; 
}

int scull_release(struct inode *inode , struct file *filp)
{
  	return 0;
}


ssize_t scull_read(struct file *filp , char __user *buf, size_t count, loff_t *f_pos)
{
  	ssize_t retval = 0;
  	if (*f_pos >= size)
    		goto out;
  	if (*f_pos + count > size)
    		count = size - *f_pos;
  	if (copy_to_user(buf, store + *f_pos , count)) {
    		retval = -EFAULT;
    		goto out;
  	}
  	*f_pos += count;
  	retval = count;
	out:
  	return retval;
}

ssize_t scull_write(struct file *filp , const char __user *buf,size_t count ,loff_t *f_pos)
{
  	ssize_t retval = -ENOMEM; 
  	if (*f_pos >= MAX_SIZE)
    		goto out;
  	if (*f_pos + count > MAX_SIZE)
    		count = MAX_SIZE - *f_pos;
  	if (copy_from_user(store + *f_pos , buf, count)) {
    		retval = -EFAULT;
    		goto out;
  	}
  	*f_pos += count;
  	retval = count;
 	
  	if (size < *f_pos)
    		size = *f_pos;
	out:
  	return retval;
}


struct file_operations scull_fops = {
  	.owner = THIS_MODULE ,
  	.read = scull_read ,
  	.write = scull_write ,
  	.open = scull_open ,
  	.release = scull_release ,
};

int scull_init_module(void)
{
  	int result;
  	dev_t dev = 0;//在linux中是unsigned int 类型,32位,用于在驱动程序中定义设备编号,高12位为主设备号,低20位为次设备号
  	
  	result = alloc_chrdev_region(&dev, scull_minor , 1, "scull");//动态申请设备号,设备名称"scull",设备个数1,次设备号scull_minor,申请到的设备号存储在dev中。该函数返回值小于0表示申请失败。
  	scull_major = MAJOR(dev);
  	if (result < 0) {
    		printk(KERN_WARNING "scull:?can't?get?major?%dn",
           	scull_major);
    		return result;
  	}
  	
  	cdev_init(&cdev , &scull_fops);//用上面声明的scull_fops初始化cdev。
  	cdev.owner = THIS_MODULE;
  	cdev.ops = &scull_fops;
  	result = cdev_add (&cdev , dev, 1);//这个是在字符设备中添加一个设备。
  	if (result) {
    		printk(KERN_WARNING "Error?%d?adding?scull", result);
    		unregister_chrdev_region(dev, 1);
    		return result;
  	}
  	return 0; 
}

void scull_cleanup_module(void)
{
  	
  	dev_t dev;
  	cdev_del(&cdev);
  	dev = MKDEV(scull_major , scull_minor);
  	unregister_chrdev_region(dev, 1);
}

module_init(scull_init_module);
module_exit(scull_cleanup_module);

通过cat /proc/devices查询是否注册成功

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/468588.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号