Linux中将设备分为三类 分别是字符设备,块设备,网络设备
应用程序通过open,read,write等系统调用访问相应的驱动程序,而字符驱动程序通过file_operations向上提供接口。具体调用如上图
本次介绍的是字符设备驱动程序,字符设备驱动程序呢 其实就是只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。
搞懂了字符设备驱动的概念和字符设备驱动和应用程序之间的关系之后我们就可以来讨论下怎么写一个简单的字符设备驱动程序了
开发板:s3c2440
linux内核版本:linux-2.6.22
static int hello_init(void)
{
dev_t devid;
#if 0
major = register_chrdev(0, "hello", &hello_fops);
#else
if (major) {
devid = MKDEV(major, 0);
register_chrdev_region(devid, HELLO_CNT, "hello");
} else {
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
major = MAJOR(devid);
}
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, devid, HELLO_CNT);
#endif
cls = class_create(THIS_MODULE, "hello");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0");
class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1");
class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2");
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
首先跟大家讲些主设备号 和 次设备号的概念
一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设
备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各
设备。
这里使用的register_chrdev_region或者alloc_chrdev_region函数注册字符设备
其实使用register_chrdev也是可以的 但是区别是register_chrdev函数注册的话
register_chrdev函数
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
cd = __register_chrdev_region(major, 0, 256, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
*s = '!';
err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, 0, 256));
return err;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
__register_chrdev_region函数
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 = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) {
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strncpy(cd->name,name, 64);
i = major_to_index(major);
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
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 - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* 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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
函数参数分析
1.major 主设备号 如果为0 即随机分配 可以指定自己想设立的设备号
2.name 设备名称
3.fops: file operations associated with this devices文件系统的接口指针
可见register_chrdev函数一开始调用了__register_chrdev_region判断主设备号是否等于0并且有效检查设备号是否有效,注册设备到全局变量chrdevs[i]中。
然后调用注册字符设备三个步骤
字符设备的注册分为三个步骤:
(1)分配cdev: struct cdev *cdev_alloc(void);
(2)初始化cdev: void cdev_init(struct cdev *cdev, const struct file_operations *fops);
(3)添加cdev: int cdev_add(struct cdev *p, dev_t dev, unsigned count)
来注册一个字符设备 这是linux2.4内核版本的写法 这种方法简便有效 直接调用一个register_chrdev函数就可以注册字符设备 但是缺点是次设备号未做处理 此种做法会导致(major, 0), (major, 1), …, (major, 255)都对应hello_fops 而我们可以用另外一个写法就是上述做法
if (major) {
devid = MKDEV(major, 0);
register_chrdev_region(devid, HELLO_CNT, "hello");
} else {
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
major = MAJOR(devid);
}
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, devid, HELLO_CNT);
意思是可以使用两种方法分配设备号 第一种方法是
1.静态申请
int register_chrdev_region(dev_t from, unsigned count, const char *name);
/**
* 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.
*/
2.动态分配
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
静态申请就是已知设备号的情况下可以分配主设备号和次设备号,而动态分配就是事先不知道主设备号的情况下 系统为你自动分配一个空闲的设备号 主次设备号可以用以下方法获得
MAJOR(dev_t dev);
MINOR(dev_t dev);
也可以使用下列宏通过主次设备号生成dev_t:
MKDEV(int major,int minor);
对于alloc_chrdev_region函数参数分析
1.dev_t *dev linux内核中,设备号用dev_t来描述,2.6.22中定义如下:
typedef u_long dev_t;
在32位机中是4个字节,高12位表示主设备号,低12位表示次设备号。
2.unsigned baseminor :first of the requested range of minor numbers
次设备号,要在一定范围内从0开始
3.unsigned count : the number of minor numbers required
次设备号的范围
4.const char *name:设备名称
register_chrdev_region函数也是和上述差不多 就不多分析 对号入座就行
字符设备注册函数分析完 下面就是设备文件的创建
自动创建设备节点的方法
利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。
创建文件的方法就两句话:“先创建一个类 然后在类下创建设备”
static struct class *cls;
cls = class_create(THIS_MODULE, "hello");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0");
当然也可以手工创建
使用mknod手工创建:mknod filename type major minor
接下来就是注销函数
static void hello_exit(void)
{
class_device_destroy(cls, MKDEV(major, 0));
class_device_destroy(cls, MKDEV(major, 1));
class_device_destroy(cls, MKDEV(major, 2));
class_destroy(cls);
cdev_del(&hello_cdev);
unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
上面只是向系统注册了一个字符设备 分配了设备号 并且创建了设备节点 但是应用程序不能访问它 所以我们需要file_operations结构体
struct file_operations ***_ops={
.owner = THIS_MODULE,
.llseek = ***_llseek,
.read = ***_read,
.write = ***_write,
.ioctl = ***_ioctl,
.open = ***_open,
.release = ***_release,
。。。 。。。
};
struct module *owner;
loff_t (*llseek) (struct file * filp , loff_t p, int orig);
ssize_t (*read) (struct file * filp, char __user * buffer, size_t size , loff_t * p);
ssize_t (*aio_read)(struct kiocb * , char __user * buffer, size_t size , loff_t p);
ssize_t (*write) (struct file * filp, const char __user * buffer, size_t count, loff_t * ppos);
ssize_t (*aio_write)(struct kiocb *, const char __user * buffer, size_t count, loff_t * ppos);
int (*readdir) (struct file * filp, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode * inode , struct file * filp ) ;
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int(*synch)(struct file *,struct dentry *,int datasync);
int (*aio_fsync)(struct kiocb *, int);
int (*fasync) (int, struct file *, int);
static int ***_fasync(int fd,struct file *filp,int mode)
{
struct ***_dev * dev=filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);
}
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int)
int (*dir_notify)(struct file *, unsigned long);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
在这里我们只是简单的使用了open函数
static int hello_open(struct inode *inode, struct file *file)
{
printk("hello_open\n");
return 0;
}
构造file_operations
static struct hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
};
具体想再添加对这个设备的读写或者其他功能的话就可以在file_operations里添加并且根据我们的需求添加
小结