Linux中I/O设备分为两类:块设备和字符设备。两种设备本身没有严格限制,但是基于不同的功能进行了分类。
(1)字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,调制解调器是典型的字符设备。
(2)块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。
两种设备本身并没用严格的区分,主要是字符设备和块设备驱动程序提供的访问接口是不一样的。
今天一同事说他弟弟的考试作业是写一个字符驱动程序,有以下要求:
(1)模块加载成功后,使用cat /proc/devices可以看到设备test,并可以看到它的主设备号;
(2)mknod创建设备文件:mknod /dev/testdev c major minor,可以通过ls –hl /dev/testdev查看;
(3)测试程序可以对设备进行读写;
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
static int major = 0;
static int minor = 0;
static char devname[] = "cdev";
struct cdevice
{
int value;
struct cdev cdev;
};
static struct cdevice *mydev;
static int cdevice_open(struct inode *inode, struct file *filp);
static int cdevice_release(struct inode *inode, struct file *filp);
static ssize_t cdevice_read(struct file *filp, char *buf, size_t size, loff_t *ppos);
static ssize_t cdevice_write(struct file *filp, const char *buf, size_t size, loff_t *ppos);
struct file_operations cdevice_fops = {
owner: THIS_MODULE,
open: cdevice_open,
release: cdevice_release,
read: cdevice_read,
write: cdevice_write,
};
static int cdevice_init(void)
{
int ret;
dev_t devno;
devno = MKDEV(major, minor);
if (major) {
//按指定的major号申请
ret = register_chrdev_region(devno, 1, devname);
} else {
//动态分配,并保存major号
ret = alloc_chrdev_region(&devno, minor, 1, devname);
major = MAJOR(devno);
}
if (ret < 0) {
printk("[cdevice_init]: cdev register failed\n");
return ret;
}
/*global variable*/
mydev = kmalloc(sizeof(struct cdevice), GFP_KERNEL);
if (mydev == NULL) {
ret =- ENOMEM;
} else {
mydev->value = 0;
mydev->cdev.owner = THIS_MODULE;
cdev_init(&mydev->cdev, &cdevice_fops);
ret = cdev_add(&mydev->cdev, devno, 1);
if (ret < 0) {
printk("[cdevice_init]: add device failed\n");
}
}
printk("[cdevice_init]: cdev register success\n");
return ret;
}
static void cdevice_exit(void)
{
dev_t devno;
cdev_del(&mydev->cdev);
kfree(mydev);
devno = MKDEV(major, minor);
unregister_chrdev_region(devno, 1);
printk("[cdevice_exit]: cdev unregister success\n");
}
static int cdevice_open(struct inode *inode, struct file *filp)
{
struct cdevice *dev;
dev = container_of(inode->i_cdev, struct cdevice, cdev);
filp->private_data = dev;
printk("[cdevice_open]: success\n");
return 0;
}
static int cdevice_release(struct inode *inode, struct file *filp)
{
printk("[cdevice_release]: success\n");
return 0;
}
static ssize_t cdevice_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
struct cdevice *dev;
unsigned long count;
dev = filp->private_data;
count = sizeof(int);
if (copy_to_user(buf, &dev->value, count)) {
return -EFAULT;
}
return count;
}
static ssize_t cdevice_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
struct cdevice *dev;
unsigned long count;
dev = filp->private_data;
count = sizeof(int);
if (copy_from_user(&dev->value, buf, count)) {
return -EFAULT;
}
return count;
}
module_init(cdevice_init);
module_exit(cdevice_exit);
MODULE_LICENSE("GPL");
ifneq ($(KERNELRELEASE), )
obj-m := driver.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *.mod.c *.mod.o *.ko *.order *.symvers .*.cmd .tmp_versions
endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main(void)
{
int fd, num;
fd = open("/dev/cdev", O_RDWR);
if (fd == -1) {
printf("%s\n", strerror(errno));
return -1;
}
read(fd, &num, sizeof(int));
printf("[read]: value = %d\n", num);
printf("[write]: Please input a num: ");
scanf("%d", &num);
write(fd, &num, sizeof(int));
read(fd, &num, sizeof(int));
printf("[read]: value = %d\n", num);
close(fd);
}
struct file和struct inode是设备驱动中二个最重要的数据结构。file结构代表一个打开的文件,它由内核在 open 时创建,并传递给在文件上操作的任何函数,直到最后的关闭。在文件的所有实例都关闭后, 内核释放这个数据结构;
inode结构由内核在内部用来表示文件.inode 结构包含大量关于文件的信息其中dev_t i_rdev成员包含实际的设备编号.struct cdev *i_cdev中struct cdev 是内核的内部结构,代表字符设备。
struct file_operations结构中的每个成员必须指向驱动中的函数,这些函数实现一个特别的操作,或者对于不支持的操作留置为 NULL。当指定为 NULL 指针时内核的确切的行为是每个函数不同的,该结构中主要函数如下:
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
filp 是文件指针,count 是请求的传输数据大小,buff 参数指向持有被写入数据的缓存,或者放入新数据的空缓存。最后 offp 是一个指针指向一个“long offset type”对象,它指出用户正在存取的文件位置, 返回值是一个"signed size type"。
int register_chrdev_region(dev_t first, unsigned int count, char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name) ;
void unregister_chrdev_region(dev_t first, unsigned int count);
允许驱动分配和释放设备编号的范围的函数。register_chrdev_region 需要知道设置的主编号,对于动态分配, 使用 alloc_chrdev_region 代替。
来自Linux Kernel中的container_of宏定义, 用结构体成员的地址,结构体的类型,以及这个结构体成员在结构体当中声明的名字, 这三个参数来获取结构体的地址。和nginx的ngx_queue_data的功能相似。
item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
#define ngx_queue_data(q, type, link) \
(type *) ((u_char *) q - offsetof(type, link))
每个文件都有2个设备号,第1个是主设备号,标识驱动程序,第2个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备。
http://www.oschina.net/code/snippet_579503_22177
http://www.2cto.com/kf/201303/198949.html
http://blog.csdn.net/bonnshore/article/details/7860997
http://rangercyh.blog.51cto.com/1444712/521244
http://oss.org.cn/kernel-book/ldd3/ch01s03.html
http://blog.csdn.net/waldmer/article/details/17611579