linux kernel 字符设备详解
有关Linux kernel 字符设备分析:
参考:http://blog.jobbole.com/86531/
一.linux kernel 将设备分为3大类,字符设备,块设备,网络设备.
字符设备是指只能一个字节一个字节读写的设备, 常见的外设基本上都是字符设备.
块设备:常见的存储设备,硬盘,SD卡都归为块设备,块设备是按一块一块读取的.
网络设备:linux 将对外通信的一个机制抽象成一个设备, 通过套接字对其进行相关的操作.
每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。
二、字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系。
三.字符设备的模型
四.下面讲一下字符设备驱动的编写流程,linux 内核为字符设备的创建提供了一套接口.
首先介绍一下dev_t , 他是主设备号和次设备号的结构体生成,他就代表了一个主次设备号
通过函数MKDEV (MAJ , MINOR) ; 生成.我们注册一个字符设备可以通过动态注册也可以静态注册 , linux kernel 为我们提供了所需要的接口
首先讲一下静态注册的方法
//分配主设备号为200 次设备号从5开始分配5个 设备名字叫MONEY
int ret ;
DeviceId = MKDEV(MAJ , BASEMINOR);
ret = register_chrdev_region(DeviceId , MINORCNT , "MONEY");
if(ret < )
{
return ret ;
} //********************************************
//方法一
//1> 初始化
cdev_init(&device, &fops);
//2> 添加 domain->probes HASH表上
cdev_add(&device,DeviceId , MINORCNT);
通过函数register_chrdev_region() , 我们可以注册主设备号为MAJ , 次设备号BASEMINOR 开始 , 一共注册MINORCNT 个次设备号 , 名字为MONEY 的字符设备.
第二种方法是动态申请主次设备号:
//动态分配一个主设备号
int ret ;
ret = alloc_chrdev_region(&DeviceId , BASEMINOR , MINORCNT,"TONY");
if(ret < )
{
return ret ;
} printk("major:%d \n" , MAJOR(DeviceId)); //********************************************
//方法二
//1> 分配空间
device = cdev_alloc();
我们可以通过linux kernel 提供的alloc_chrdev_region () 的方法 , 申请一个主设备号和基础设备号 , 一共申请 MINORCNT , 名字为 TONY 的一个字符设备.
这里涉及一个结构体:
struct cdev {
struct kobject kobj;
struct module *owner;
4 const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
这里的话还要申请一个cdev 结构体的空间
通过cdev_alloc() ;
搞定了主次设备号的问题 , 接下来就是涉及到了初始化和添加到设备列表 .
linux kernel 为我们提供了以下的方法:
//2> 初始化
cdev_init(device, &fops);
//3> 添加 domain->probes HASH表上
cdev_add(device,DeviceId , MINORCNT);
这里面涉及到了一个&fops 的文件操作结构体
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = myopen,
.read = myread ,
.write = mywrite,
.release = myclose,
.unlocked_ioctl = myioctl,
};
上层的open read write 等函数经过一系列的转换都会到对应的函数
相对应的, 释放主次设备号, 删除在设备列表的节点, linux kernel 为我们提供了一下接口:
cdev_del(device); unregister_chrdev_region(DeviceId , MINORCNT);
下面的代码是想深入了解里面的代码的看看就行了 , 如果只是向了解接口的 , 上面的就行了
从静态申请注册开始跟起吧
#define MKDEV(ma,mi) ((ma)<<8 | (mi))
上面这个是制作一个主次设备号的结构体
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern int register_chrdev_region(dev_t, unsigned, const char *);
extern int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops);
extern void __unregister_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name);
extern void unregister_chrdev_region(dev_t, unsigned);
这是几个将要用的函数的函数声明 , 它在 include/linux/fs.h 文件中
首先看一下 register_chrdev_region() 函数
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
注释说明: 注册一个范围的设备号 , 原型如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next; for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+, );
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return ;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+, );
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
它是调用了
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
进里面看看
/*
* Register a single major with a specified minor range.
*
* If major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If major > 0 this function will attempt to reserve the passed range of
* minors and will return zero on success.
*
* Returns a -ve errno on failure.
*/
还是看注释: 注册一个指定的主设备号 和一个指定的次设备号范围
判断 主设备号是不是为零 , 如果是零的话 就动态申请一个主设备号 , 这就是后面要讲的那个动态申请 , 它也是调用了这个.
代码如下
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = ;
int i;
这里面涉及一个结构体没讲:
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); //动态申请了一个char_device_struct 结构体
if (cd == NULL)
return ERR_PTR(-ENOMEM); mutex_lock(&chrdevs_lock); //加一个互斥锁 , 防止其他进程并发 /* temporary */
if (major == ) {
for (i = ARRAY_SIZE(chrdevs)-; i > ; i--) { //这里其实就是做了一个动态申请主设备号的功能
if (chrdevs[i] == NULL)
break;
} if (i == ) { //没有申请到主设备号, 直接退出
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
} cd->major = major; //对结构体进行初始化
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name)); i = major_to_index(major); // 哈希表的下标生成 for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //进入chdevs[i] 哈希表快速进入
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break; /* Check for overlapping minor ranges. */ // 检查次设备号会不会重叠
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - ;
int new_min = baseminor;
int new_max = baseminor + minorct - ; /* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
} /* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
} cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock); //解锁
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
到这里一个主次设备号就搞定了 , 并申请一个char_device_struct 结构体, 并对其进行赋值初始化.
第二步就是对cdev 进行init :
void cdev_init(struct cdev *, const struct file_operations *);
这里又涉及到一个struct cdev 的结构体:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
进初始化代码一看究竟:
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, , sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
看一段代码之前我们尽可能的先看注释, 这样会让我们跟代码轻松很多 , 我们可以顺着代码的作者的思路走
注释: 初始化一个cdev 结构体
进kobject_init() 看看:
/**
* kobject_init - initialize a kobject structure 初始化一个内核项目结构体
* @kobj: pointer to the kobject to initialize
* @ktype: pointer to the ktype for this kobject.
*
* This function will properly initialize a kobject such that it can then
* be passed to the kobject_add() call.
*
* After this function is called, the kobject MUST be cleaned up by a call
* to kobject_put(), not by a call to kfree directly to ensure that all of
* the memory is cleaned up properly.
*/
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str; if (!kobj) {
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) {
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
dump_stack();
} kobject_init_internal(kobj);
kobj->ktype = ktype;
return; error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
EXPORT_SYMBOL(kobject_init);
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:;
unsigned int state_in_sysfs:;
unsigned int state_add_uevent_sent:;
unsigned int state_remove_uevent_sent:;
unsigned int uevent_suppress:;
};
下一部就是 cdev_add () ;
int cdev_add(struct cdev *, dev_t, unsigned);
linux kernel 字符设备详解的更多相关文章
- linux 开机启动过程详解
Linux开机执行内核后会启动init进程,该进程根据runlevel(如x)执行/etc/rcx.d/下的程序,其下的程序是符号链接,真正的程序放在/etc/init.d/下.开机启动的程序(服务等 ...
- Linux开机启动程序详解
Linux开机启动程序详解我们假设大家已经熟悉其它操作系统的引导过程,了解硬件的自检引导步骤,就只从Linux操作系统的引导加载程序(对个人电脑而言通常是LILO)开始,介绍Linux开机引导的步骤. ...
- Linux开机启动程序详解[转]
Linux开机启动程序详解 我们假设大家已经熟悉其它操作系统的引导过程,了解硬件的自检引导步骤,就只从Linux操作系统的引导加载程序(对个人电脑而言通常是LILO)开始,介绍Linux开机引导的步骤 ...
- linux系统设置服务开机启动3种方法,Linux开机启动程序详解
linux系统设置服务开机启动 方法1:.利用ntsysv伪图形进行设置,利用root登陆 终端命令下输入ntsysv 回车:如下图 方法2:利用命令行chkconfig命令进行设置 简要说明 ...
- (转)Linux 系统设置 : dmesg 命令详解
原文:https://blog.csdn.net/yexiangCSDN/article/details/80683246 https://www.cnblogs.com/duanxz/p/34770 ...
- syslog之一:Linux syslog日志系统详解
目录: <syslog之一:Linux syslog日志系统详解> <syslog之二:syslog协议及rsyslog服务全解析> <syslog之三:建立Window ...
- 【转】linux中inittab文件详解
原文网址:http://www.2cto.com/os/201108/98426.html linux中inittab文件详解 init的进程号是1(ps -aux | less),从这一点就能看出, ...
- Linux下桥接模式详解一
注册博客园已经好长时间,一直以来也没有在上面写过文章,都是随意的记录在了未知笔记上,今天开始本着分享和学习的精神想把之前总结的笔记逐步分享到博客园,和大家一起学习,一起进步吧! 2016-09-20 ...
- (转)Linux curl命令参数详解
Linux curl命令参数详解 命令:curl在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一款很强大的http命令行工具.它支持文件的上传和下载,是综合传输工具, ...
随机推荐
- 有了这个,再也不用每次连新机器都要设置secure crt属性了
我连服务器用的是secure crt,每次ssh新服务器的时候都得手动设置字符编码和背景颜色,今天问了旁边的开发原来可以全局设置,以后连服务器的时候就再也不用手动设置相关属性了.步骤如下: 一开始点击 ...
- 虚拟机NUMA和内存KSM
KSM技术可以合并相同的内存页,即使是不同的NUMA节点,如果需要关闭跨NUMA节点的内存合并,设置/sys/kernel/mm/ksm/merge_across_nodes参数为0.或者可以关闭特定 ...
- ASP.NET技巧:教你制做Web实时进度条
网上已经有很多Web进度条的例子,但是很多都是估算时间,不能正真反应任务的真实进度.我自己结合多线程和ShowModalDialog制做了 一个实时进度条,原理很简单:使用线程开始长时间的任务,定义一 ...
- centos 7.0 编译 安装mysql 5.6.22 过程 已完成~ 成功~ 撒花~
mysql 下载目录/usr/local/srcmysql 解压目录 /usr/local/bin/mysql GitHub https://github.com/mysql/mysql-server ...
- Java-java中无符号类型的处理
在Java中,不存在Unsigned无符号数据类型,但可以轻而易举的完成Unsigned转换. 方案一:如果在Java中进行流(Stream)数据处理,可以用DataInputStream类对Stre ...
- XPath使用小结
参考资料: http://www.w3school.com.cn/xpath/xpath_nodes.asp
- 解决DWZ(JUI)的panel 点击关闭或者打开按钮 自己写的标签消失
问题描述:DWZ的panel面板比较常用,我们常常需要在其标题栏上再增加一个些按钮,如下图问题出来了,增加按钮后,点面板收缩按钮,增加的按钮就消失了而且面板收缩的click事件,也跟新增的按钮绑定了, ...
- Linux下vim查看文件名
在vim下编辑时,有时候看不到文件名,不知道编辑的是那个文件,怎么呢,可以按照下面的方法试试. 查看文件名 在正常模式下: :f 或CTRL+G 查看文件的路径 用 :!pwd 可以看当前的详细路径. ...
- Linux下更新时间
方法一.使用命令 ntpdate time-a.nist.gov 方法二.本地安装ntpdate客户端 在本地安装ntpdate客户端,更新时用 ntpdate cn.pool.ntp.org 如果你 ...
- ApacheServer-----关于443端口被占用的解决方法
最经公司项目需要经过Apache服务器转发,自己也下载了ApacheServer,但是在启动的过程中,遇到443端口被占用,网上看了一些解决方法,都不对,没有解决问题. 执行启动命令httpd -k ...