linux kernel 字符设备详解

有关Linux kernel 字符设备分析:linux

参考:http://blog.jobbole.com/86531/网络

一.linux kernel 将设备分为3大类,字符设备,块设备,网络设备.并发

linux kernel 字符设备详解

字符设备是指只能一个字节一个字节读写的设备, 常见的外设基本上都是字符设备.app

块设备:常见的存储设备,硬盘,SD卡都归为块设备,块设备是按一块一块读取的.dom

网络设备:linux 将对外通讯的一个机制抽象成一个设备, 经过套接字对其进行相关的操做.ide

每个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序经过设备文件(或称设备节点)来使用驱动程序操做字符设备和块设备。函数

2、字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系。ui

linux kernel 字符设备详解

三.字符设备的模型this

linux kernel 字符设备详解

四.下面讲一下字符设备驱动的编写流程,linux 内核为字符设备的建立提供了一套接口.3d

首先介绍一下dev_t , 他是主设备号和次设备号的结构体生成,他就表明了一个主次设备号

经过函数MKDEV (MAJ , MINOR) ; 生成.咱们注册一个字符设备能够经过动态注册也能够静态注册 , linux kernel 为咱们提供了所须要的接口

首先讲一下静态注册的方法
复制代码

1 //分配主设备号为200 次设备号从5开始分配5个 设备名字叫MONEY
2 int ret ;
3 DeviceId = MKDEV(MAJ , BASEMINOR);
4 ret = register_chrdev_region(DeviceId , MINORCNT , "MONEY");
5 if(ret < 0)
6 {
7 return ret ;
8 }
9
10 //****
11 //方法一
12 //1> 初始化
13 cdev_init(&device, &fops);
14 //2> 添加 domain->probes HASH表上
15 cdev_add(&device,DeviceId , MINORCNT);

复制代码

经过函数register_chrdev_region() , 咱们能够注册主设备号为MAJ , 次设备号BASEMINOR 开始 , 一共注册MINORCNT 个次设备号 , 名字为MONEY 的字符设备.

第二种方法是动态申请主次设备号:
复制代码

1 //动态分配一个主设备号
2 int ret ;
3 ret = alloc_chrdev_region(&DeviceId , BASEMINOR , MINORCNT,"TONY");
4 if(ret < 0)
5 {
6 return ret ;
7 }
8
9 printk("major:%d \n" , MAJOR(DeviceId));
10
11 //****
12 //方法二
13 //1> 分配空间
14 device = cdev_alloc();

复制代码

咱们能够经过linux kernel 提供的alloc_chrdev_region () 的方法 , 申请一个主设备号和基础设备号 , 一共申请 MINORCNT , 名字为 TONY 的一个字符设备.

这里涉及一个结构体:
复制代码

1 struct cdev {
2 struct kobject kobj;
3 struct module owner;
4 const struct file_operations
ops;
5 struct list_head list;
6 dev_t dev;
7 unsigned int count;
8 };

复制代码

这里的话还要申请一个cdev 结构体的空间

经过cdev_alloc() ;

搞定了主次设备号的问题 , 接下来就是涉及到了初始化和添加到设备列表 .

linux kernel 为咱们提供了如下的方法:

1 //2> 初始化
2 cdev_init(device, &fops);
3 //3> 添加 domain->probes HASH表上
4 cdev_add(device,DeviceId , MINORCNT);
5

这里面涉及到了一个&fops 的文件操做结构体
复制代码

1 static struct file_operations fops = {
2 .owner = THIS_MODULE,
3 .open = myopen,
4 .read = myread ,
5 .write = mywrite,
6 .release = myclose,
7 .unlocked_ioctl = myioctl,
8 };

复制代码

上层的open read write 等函数通过一系列的转换都会到对应的函数

相对应的, 释放主次设备号, 删除在设备列表的节点, linux kernel 为咱们提供了一下接口:

1 cdev_del(device);
2
3 unregister_chrdev_region(DeviceId , MINORCNT);

下面的代码是想深刻了解里面的代码的看看就好了 , 若是只是向了解接口的 , 上面的就好了

从静态申请注册开始跟起吧

1 #define MKDEV(ma,mi) ((ma)<<8 | (mi))

上面这个是制做一个主次设备号的结构体

复制代码

1 extern int alloc_chrdev_region(dev_t , unsigned, unsigned, const char );
2 extern int register_chrdev_region(dev_t, unsigned, const char );
3 extern int __register_chrdev(unsigned int major, unsigned int baseminor,
4 unsigned int count, const char
name,
5 const struct file_operations fops);
6 extern void __unregister_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char
name);
7 extern void unregister_chrdev_region(dev_t, unsigned);

复制代码

这是几个将要用的函数的函数声明 , 它在 include/linux/fs.h 文件中

首先看一下 register_chrdev_region() 函数
复制代码

1 /*
2
register_chrdev_region() - register a range of device numbers
3 @from: the first in the desired range of device numbers; must include
4
the major number.
5 @count: the number of consecutive device numbers required
6
@name: the name of the device or driver.
7
8
Return value is zero on success, a negative error code on failure.
9 */

复制代码

注释说明: 注册一个范围的设备号 , 原型以下:
复制代码

1 int register_chrdev_region(dev_t from, unsigned count, const char name)
2 {
3 struct char_device_struct
cd;
4 dev_t to = from + count;
5 dev_t n, next;
6
7 for (n = from; n < to; n = next) { next = MKDEV(MAJOR(n)+1, 0);
8 if (next > to)
9 next = to;
10 cd = __register_chrdev_region(MAJOR(n), MINOR(n),
11 next - n, name);
12 if (IS_ERR(cd))
13 goto fail;
14 }
15 return 0;
16 fail:
17 to = n;
18 for (n = from; n < to; n = next) {
19 next = MKDEV(MAJOR(n)+1, 0);
20 kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
21 }
22 return PTR_ERR(cd);
23 }

复制代码

它是调用了

cd = __register_chrdev_region(MAJOR(n), MINOR(n),

进里面看看
复制代码

1 /
2
Register a single major with a specified minor range.
3
4
If major == 0 this functions will dynamically allocate a major and return
5 its number.
6

7 If major > 0 this function will attempt to reserve the passed range of
8
minors and will return zero on success.
9
10
Returns a -ve errno on failure.
11 */

复制代码

仍是看注释: 注册一个指定的主设备号 和一个指定的次设备号范围

判断 主设备号是否是为零 , 若是是零的话 就动态申请一个主设备号 , 这就是后面要讲的那个动态申请 , 它也是调用了这个.

代码以下
复制代码

1 static struct char_device_struct
2 __register_chrdev_region(unsigned int major, unsigned int baseminor,
3 int minorct, const char
name)
4 {
5 struct char_device_struct *cd, **cp;
6 int ret = 0;
7 int i;
8
这里面涉及一个结构体没讲:

复制代码

1 static struct char_device_struct {
2 struct char_device_struct next;
3 unsigned int major;
4 unsigned int baseminor;
5 int minorct;
6 char name[64];
7 struct cdev
cdev; / will die /
8 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

复制代码

9 cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); //动态申请了一个char_device_struct 结构体
10 if (cd == NULL)
11 return ERR_PTR(-ENOMEM);
12
13 mutex_lock(&chrdevs_lock); //加一个互斥锁 , 防止其余进程并发
14
15 / temporary /
16 if (major == 0) {
17 for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) { //这里其实就是作了一个动态申请主设备号的功能
18 if (chrdevs[i] == NULL)
19 break;
20 }
21
22 if (i == 0) {      //没有申请到主设备号, 直接退出
23 ret = -EBUSY;
24 goto out;
25 }
26 major = i;
27 ret = major;
28 }
29
30 cd->major = major;                  //对结构体进行初始化
31 cd->baseminor = baseminor;
32 cd->minorct = minorct;
33 strlcpy(cd->name, name, sizeof(cd->name));
34
35 i = major_to_index(major);      // 哈希表的下标生成
36
37 for (cp = &chrdevs[i]; cp; cp = &(cp)->next)    //进入chdevs[i] 哈希表快速进入
38 if ((cp)->major > major ||
39 ((
cp)->major == major &&
40 (((cp)->baseminor >= baseminor) ||
41 ((
cp)->baseminor + (cp)->minorct > baseminor))))
42 break;
43
44 /
Check for overlapping minor ranges. /      // 检查次设备号会不会重叠
45 if (
cp && (cp)->major == major) {
46 int old_min = (
cp)->baseminor;
47 int old_max = (cp)->baseminor + (cp)->minorct - 1;
48 int new_min = baseminor;
49 int new_max = baseminor + minorct - 1;
50
51 / New driver overlaps from the left. /
52 if (new_max >= old_min && new_max <= old_max) {
53 ret = -EBUSY;
54 goto out;
55 }
56
57 / New driver overlaps from the right. /
58 if (new_min <= old_max && new_min >= old_min) {
59 ret = -EBUSY;
60 goto out;
61 }
62 }
63
64 cd->next = cp;
65
cp = cd;
66 mutex_unlock(&chrdevs_lock);      //解锁
67 return cd;
68 out:
69 mutex_unlock(&chrdevs_lock);
70 kfree(cd);
71 return ERR_PTR(ret);
72 }

复制代码

到这里一个主次设备号就搞定了 , 并申请一个char_device_struct 结构体, 并对其进行赋值初始化.

第二步就是对cdev 进行init :

1 void cdev_init(struct cdev , const struct file_operations );

这里又涉及到一个struct cdev 的结构体:
复制代码

1 struct cdev {
2 struct kobject kobj;
3 struct module owner;
4 const struct file_operations
ops;
5 struct list_head list;
6 dev_t dev;
7 unsigned int count;
8 };

复制代码

进初始化代码一看究竟:
复制代码

1 /*
2
cdev_init() - initialize a cdev structure
3 @cdev: the structure to initialize
4
@fops: the file_operations for this device
5
6
Initializes @cdev, remembering @fops, making it ready to add to the
7 system with cdev_add().
8
/
9 void cdev_init(struct cdev cdev, const struct file_operations fops)
10 {
11 memset(cdev, 0, sizeof *cdev);
12 INIT_LIST_HEAD(&cdev->list);
13 kobject_init(&cdev->kobj, &ktype_cdev_default);
14 cdev->ops = fops;
15 }

复制代码

看一段代码以前咱们尽量的先看注释, 这样会让咱们跟代码轻松不少 , 咱们能够顺着代码的做者的思路走

注释: 初始化一个cdev 结构体

进kobject_init() 看看:
复制代码

1 /*
2
kobject_init - initialize a kobject structure 初始化一个内核项目结构体
3 @kobj: pointer to the kobject to initialize
4
@ktype: pointer to the ktype for this kobject.
5
6
This function will properly initialize a kobject such that it can then
7 be passed to the kobject_add() call.
8

9 After this function is called, the kobject MUST be cleaned up by a call
10
to kobject_put(), not by a call to kfree directly to ensure that all of
11 the memory is cleaned up properly.
12
/

复制代码

复制代码

1 void kobject_init(struct kobject kobj, struct kobj_type ktype)
2 {
3 char err_str;
4
5 if (!kobj) {
6 err_str = "invalid kobject pointer!";
7 goto error;
8 }
9 if (!ktype) {
10 err_str = "must have a ktype to be initialized properly!\n";
11 goto error;
12 }
13 if (kobj->state_initialized) {
14 /
do not error out as sometimes we can recover */
15 printk(KERN_ERR "kobject (%p): tried to init an initialized "
16 "object, something is seriously wrong.\n", kobj);
17 dump_stack();
18 }
19
20 kobject_init_internal(kobj);
21 kobj->ktype = ktype;
22 return;
23
24 error:
25 printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
26 dump_stack();
27 }
28 EXPORT_SYMBOL(kobject_init);

复制代码

复制代码

1 struct kobject {
2 const char name;
3 struct list_head entry;
4 struct kobject
parent;
5 struct kset kset;
6 struct kobj_type
ktype;
7 struct sysfs_dirent *sd;
8 struct kref kref;
9 unsigned int state_initialized:1;
10 unsigned int state_in_sysfs:1;
11 unsigned int state_add_uevent_sent:1;
12 unsigned int state_remove_uevent_sent:1;
13 unsigned int uevent_suppress:1;
14 };

复制代码

相关文章
相关标签/搜索