一、介绍:
实现字符设备的读写其实就是实现驱动中的 file_operation 结构体里的read和write成员。
二、实现例子:
1. 驱动层:--相当于下边的 char_read.c 文件
①先实现 file_operations 结构体中列的我们想要实现的函数,比如
static int hello_open (struct inode *inode, struct file *file)
static int hello_release (struct inode *inode, struct file *file)
ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
ssize_t hello_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
②关联上 file_operations 中的入口:
struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write
};
③注册设备号并填充 cdev 结构体:
dev_t devno = MKDEV (hello_major, hello_minor);
result = register_chrdev_region (devno, number_of_devices, "hello");
cdev_init (&cdev, &hello_fops); //两个THIS_MODULE填充上设备号,hello_fops填充上file_operations(字符设备操作集合)
cdev.owner = THIS_MODULE;
④注册 cdev 结构体:
error = cdev_add (&cdev, devno , 1);
这一步之后,我们关联的函数的符号就放到了应用程序可以调用的地方了。
⑤卸载的时候,先注销 cdev 结构体,再注销设备号
cdev_del (&cdev);
unregister_chrdev_region (devno, number_of_devices);
如果是上边的这种思路,还需要手动的创建设备节点,如果需要自动的在程序中创建设备节点,用如下的函数:device_create函数(linux2.6之后版本):-- 创建设备节点(设备节点所在的位置是 /dev 下)
所需库:
linux/device.h
原型:
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
参数:
①cls,指向将要注册设备节点的类
②parent,指向这个新的设备节点的类的父类,如果有的话,没有则是NULL
③devt,将要添加的字符设备的设备号
④drvdata,和类设备有关的设备的数据的指针(a pointer to a struct device that is assiociated with this class device.)
⑤fmt,要注册的设备要显示的节点名字
例子:
device_create(my_class,NULL,devno,NULL,"hello");
class_create函数:-- 创建类(在 /sys/class/ 下)
头文件:
linux/device.h
原型:
struct class *class_create(struct module *owner, const char *name)
owner,pointer to the module that is to "own" this struct class
name,pointer to a string for the name of this class.
例子:
struct class *my_class = class_create(THIS_MODULE,"char_hello");
他们相应的删除函数:
device_destroy(my_class, devno);
class_destroy(my_class);
这两个的顺序不能调换
2. 用户层函数:-- 相当于下边的 test.c 文件
2.1 采用访问文件 IO 的方式打开文件
int fd;
fd = open ("/dev/hello",O_RDWR);
第一个参数是创建的节点
第二个参数:
O_RDONLY:表示对文件只读
O_WRONLY:表示对文件只写
O_RDWR:表示对文件可读可写
上边这3个标志位是互斥的
O_CREAT:当文件不存在时,创建文件
O_EXCL:配合O_CREAT使用,当文件已经存在时创建,那么就会出错。
O_TRUNC:如果文件已经存在,先删除源文件内容。
O_APPEND:以添加方式打开,对文件的写操作都是在文件的末尾进行
2.2 写:
write:--向文件描述符中写入数据
原型:
ssize_t write(int fd,void *buf,size_t count);
参数:
fd,要写的文件描述符
buf,存放要写的数据
count,期望执行一次write要写的字节个数
返回值:
实际写入的字节个数。
例子:
if (write (fd, buff, strlen(buff)) < 0)
{
perror("fail to write");
}
2.3 读:
read:--读文件,ssize其实就是int型,从文件描述符中读取数据
原型:
ssize_t read(int fd,void *buf,size_t count);
参数:
fd,要读的文件描述符
buf,存放读取到的数据
count,期望执行一次read要读取的字节个数
返回值:
实际读到的字节个数。
0,网络上断开连接返回
-1,没有独到
特点:
read读取数据和\n,\0没任何关系。
例子
printf ("Read returns %d\n", read (fd, buf, sizeof(buf)));
2.4 文件描述符关闭:
close (fd);
3. 程序:
char_read.c
- MODULE_LICENSE ("GPL");
- int hello_major = 250;
- int hello_minor = 0;
- int number_of_devices = 1;
- char read_data[128] = "foobar not equal to barfoo";
- char write_data[128] = {};
- struct cdev cdev;
- struct class *my_class;
- dev_t devno;
- static int hello_open (struct inode *inode, struct file *file)
- {
- printk (KERN_INFO "Hey! device opened\n");
- return 0;
- }
- static int hello_release (struct inode *inode, struct file *file)
- {
- printk (KERN_INFO "Hmmm... device closed\n");
- return 0;
- }
- ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
- {
- ssize_t result = 0;
- if (count > 127) count = 127;
- if (copy_to_user (buff, read_data, count))
- {
- result = -EFAULT;
- }
- else
- {
- printk (KERN_INFO "wrote %d bytes\n", (int)count);
- result = count;
- }
- return result;
- }
- ssize_t hello_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
- {
- ssize_t ret = 0;
- if (count > 127) return -ENOMEM;
- if (copy_from_user (write_data, buf, count)) {
- ret = -EFAULT;
- }
- else {
- write_data[count] = '\0';
- printk (KERN_INFO"Received: %s\n", write_data);
- ret = count;
- }
- return ret;
- }
- struct file_operations hello_fops = {
- .owner = THIS_MODULE,
- .open = hello_open,
- .release = hello_release,
- .read = hello_read,
- .write = hello_write
- };
- static void char_reg_setup_cdev (void) /* 在系统中添加一个设备 */
- {
- int error;
- cdev_init (&cdev, &hello_fops);
- cdev.owner = THIS_MODULE;
- error = cdev_add (&cdev, devno, number_of_devices);
- if (error)
- printk (KERN_NOTICE "Error %d adding char_reg_setup_cdev", error);
- }
- static int char_dev_create (void) /* 为设备自动创建节点 */
- {
- my_class = class_create(THIS_MODULE,"char_hello"); // 类名为char_class
- if(IS_ERR(my_class))
- {
- printk("Err: failed in creating class.\n");
- return -1;
- }
- device_create(my_class,NULL,devno,NULL,"hello"); // 设备名为hello
- return 0;
- }
- static int __init hello_2_init (void)
- {
- int result;
- devno = MKDEV (hello_major, hello_minor);
- result = register_chrdev_region (devno, number_of_devices, "hello");
- if (result < 0) {
- printk (KERN_WARNING "hello: can't get major number %d\n", hello_major);
- return result;
- }
- char_dev_create();
- char_reg_setup_cdev();
- printk (KERN_INFO "char device registered\n");
- return 0;
- }
- static void __exit hello_2_exit (void)
- {
- cdev_del (&cdev);
- device_destroy(my_class, devno); //delete device node under /dev//必须先删除设备,再删除class类
- class_destroy(my_class); //delete class created by us
- unregister_chrdev_region (devno, number_of_devices);
- printk (KERN_INFO "char driver cleaned up\n");
- }
- module_init (hello_2_init);
- module_exit (hello_2_exit);
Makefile
- ifeq ($(KERNELRELEASE),)
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- #KERNELDIR ?= ~/wor_lip/linux-3.4.112
- PWD := $(shell pwd)
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- modules_install:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules* Module*
- .PHONY: modules modules_install clean
- else
- obj-m := char_read.o
- endif
test.c
- int main (void)
- {
- int n, fd;
- char read_buf[256] ={};
- char *p =NULL;
- char write_buff[256]="beijing 15071 bu yao shui jiao";
- puts("1:this is read_buf:");
- printf("read_buf = %s\n", read_buf);
- fd = open ("/dev/hello", O_RDWR);
- if (fd < 0) {
- perror("open");
- exit(0);
- }
- printf ("\n/dev/hello opened, fd=%d\n", fd);
- /* read */
- printf ("Read returns %d\n", (int)read (fd, read_buf, sizeof(read_buf)));
- puts("\n2:this is read_buf:");
- printf("read_buf = %s\n", read_buf);
- /* write */
- printf("write buffer: %s\n", write_buff);
- if (write (fd, write_buff, strlen(write_buff)) < 0)
- {
- perror("fail to write");
- }
- close (fd);
- printf ("/dev/hello closed :)\n");
- return 0;
- }
将这些文件放到一个文件夹下,在文件夹下执行:
make
加载模块
sudo insmod char_read.ko
编译 test.c 文件:
gcc test.c
执行:
sudo ./a.out
执行的结果是:
1:this is read_buf: read_buf = /dev/hello opened, fd=3 Read returns 127 2:this is read_buf: read_buf = foobar not equal to barfoo write buffer: beijing 15071 bu yao shui jiao /dev/hello closed :) |
dmesg显示的结果如下:
[12432.692810] char device registered
[12444.042488] Hey! device opened
[12444.042501] wrote 127 bytes
[12444.042526] Received: beijing 15071 bu yao shui jiao
[12444.042528] Hmmm... device closed
如果你的程序中没有自动的创建设备节点,那么,你还要用mknode来自己创建设备节点:
mknod /dev/设备要显示的名称 c 主设备号 0