数据结构及关系如下:
include/linux/cdev.h 12 struct cdev { 13 struct kobject kobj; 14 struct module *owner; 15 const struct file_operations *ops; 16 struct list_head list; 17 dev_t dev; 18 unsigned int count; 19 }; include/linux/fs.h 518 /* 519 * Keep mostly read-only and often accessed (especially for 520 * the RCU path lookup and 'stat' data) fields at the beginning 521 * of the 'struct inode' 522 */ 523 struct inode { 595 union { 596 struct pipe_inode_info *i_pipe; 597 struct block_device *i_bdev; 598 struct cdev *i_cdev; 599 }; 612 }; include/linux/fs.h 765 struct file { 777 struct inode *f_inode; /* cached value */ 800 /* needed for tty driver, and maybe others */ 801 void *private_data; 812 };
// include/linux/cdev.h struct cdev { struct module *owner; const struct file_operations *ops; dev_t dev; // ...... };
void cdev_init(struct cdev *, const struct file_operations *);
int cdev_add(struct cdev *, dev_t, unsigned);
添加字符设备后,就可以用mknod创建文件,可以读写。
创建设备号时,需要指定设备名,即函数alloc_chrdev_region。
mknod需要知道设备名和设备号,安装驱动后,可以在/proc/devices中依据设备名查到设备号:
insmod hello.ko \ && major_device_number=`awk '$2=="winlin" {print $1}' /proc/devices` \ && mknod /dev/winlin c $major_device_number 0
cat /dev/winlin \ && echo "linux is a great operating system for server." > /dev/winlin \ && cat /dev/winlin
rm -f /dev/winlin \ && rmmod hello
完整源码如下:
// hello.c #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("GPL"); dev_t devno; int count = 1; char* name = "winlin"; /** make insmod hello.ko \ && major_device_number=`awk '$2=="winlin" {print $1}' /proc/devices` \ && mknod /dev/winlin c $major_device_number 0 cat /dev/winlin \ && echo "linux is a great operating system for server." > /dev/winlin \ && cat /dev/winlin rm -f /dev/winlin \ && rmmod hello */ /*module_param(count, int, S_IRUGO|S_IWUSR);*/ module_param(name, charp, S_IRUGO|S_IWUSR); #include <linux/fs.h> #include <linux/uaccess.h> char _data[1024] = "KDD char device buffer.\n"; ssize_t hello_read(struct file* file, char __user* data, size_t size, loff_t* offset ){ int len = strlen(_data); if(*offset >= len){ return 0; } printk(KERN_ALERT "hello_read, len=%d, size=%d, offset=%d\n", len, (int)size, (int)*offset); if(copy_to_user(data, _data, (len < size)? len:size) != 0){ return -1; } *offset += len; return len; } ssize_t hello_write(struct file* file, const char __user* data, size_t size, loff_t* offset ){ int size_to_write = (sizeof(_data) - 1 < size)? sizeof(_data) - 1:size; printk(KERN_ALERT "hello_write, len=%d, size=%d, offset=%d\n", size_to_write, (int)size, (int)*offset); if(copy_from_user(_data, data, size_to_write) != 0){ return -1; } _data[size_to_write] = 0; return size_to_write; } int hello_open(struct inode* node, struct file* file){ return 0; } int hello_release(struct inode* node, struct file* file){ return 0; } struct file_operations fops = { .owner = THIS_MODULE, .read = hello_read, .write = hello_write, .open = hello_open, .release = hello_release, }; #include <linux/cdev.h> struct cdev dev; int hello_init(void){ printk(KERN_INFO "hello, %s!\n", name); if(alloc_chrdev_region(&devno, 0, count, name) != 0){ return -1; } cdev_init(&dev, &fops); dev.owner = THIS_MODULE; //dev.ops = &fops; if(cdev_add(&dev, devno, 1) != 0){ return -1; } return 0; } void hello_exit(void){ printk(KERN_ALERT "goodbye, %s\n", name); unregister_chrdev_region(devno, count); } module_init(hello_init); module_exit(hello_exit);
Makefile如下:
ifneq ($(KERNELRELEASE),) obj-m := hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -f *.mod.* *.ko *.order *.symvers *.o endif
一个驱动可以创建多个设备,即minor不一样,major一样。代码如下:
// hello.c #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("GPL"); int major = 0; int count = 1; char* name = "winlin"; /** make insmod hello.ko count=2\ && major_device_number=`awk '$2=="winlin" {print $1}' /proc/devices` \ && mknod /dev/winlin0 c $major_device_number 0 \ && mknod /dev/winlin1 c $major_device_number 1 cat /dev/winlin0 \ && echo "[dev0] linux is a great operating system for server." > /dev/winlin0 \ && cat /dev/winlin0 cat /dev/winlin1 \ && echo "[dev1] linux is a great operating system for server." > /dev/winlin1 \ && cat /dev/winlin1 rm -f /dev/winlin* \ && rmmod hello */ module_param(count, int, S_IRUGO|S_IWUSR); module_param(name, charp, S_IRUGO|S_IWUSR); #include <linux/fs.h> #include <linux/uaccess.h> char _data[1024] = "KDD char device buffer.\n"; ssize_t hello_read(struct file* file, char __user* data, size_t size, loff_t* offset ){ int len = strlen(_data); if(*offset >= len){ return 0; } printk(KERN_ALERT "hello_read, len=%d, size=%d, offset=%d\n", len, (int)size, (int)*offset); if(copy_to_user(data, _data, (len < size)? len:size) != 0){ return -1; } *offset += len; return len; } ssize_t hello_write(struct file* file, const char __user* data, size_t size, loff_t* offset ){ int size_to_write = (sizeof(_data) - 1 < size)? sizeof(_data) - 1:size; printk(KERN_ALERT "hello_write, len=%d, size=%d, offset=%d\n", size_to_write, (int)size, (int)*offset); if(copy_from_user(_data, data, size_to_write) != 0){ return -1; } _data[size_to_write] = 0; return size_to_write; } int hello_open(struct inode* node, struct file* file){ printk(KERN_ALERT "hello_open file\n"); return 0; } int hello_release(struct inode* node, struct file* file){ printk(KERN_ALERT "hello_release file\n"); return 0; } struct file_operations fops = { .owner = THIS_MODULE, .read = hello_read, .write = hello_write, .open = hello_open, .release = hello_release, }; #include <linux/cdev.h> struct cdev dev; int hello_init(void){ int i; dev_t devno; printk(KERN_INFO "hello, %s!\n", name); if(alloc_chrdev_region(&devno, 0, count, name) != 0){ return -1; } major = MAJOR(devno); cdev_init(&dev, &fops); dev.owner = THIS_MODULE; //dev.ops = &fops; for(i = 0; i < count; i++){ if(cdev_add(&dev, MKDEV(major, i), 1) != 0){ return -1; } } return 0; } void hello_exit(void){ printk(KERN_ALERT "goodbye, %s\n", name); cdev_del(&dev); unregister_chrdev_region(MKDEV(major, 0), count); } module_init(hello_init); module_exit(hello_exit);
代码如下:
// hello.c #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("GPL"); int major = 0; int count = 1; char* name = "winlin"; /** make insmod hello.ko count=2\ && major_device_number=`awk '$2=="winlin" {print $1}' /proc/devices` \ && mknod /dev/winlin0 c $major_device_number 0 \ && mknod /dev/winlin1 c $major_device_number 1 cat /dev/winlin0 \ && echo "[dev0] linux is a great operating system for server." > /dev/winlin0 \ && cat /dev/winlin0 cat /dev/winlin1 \ && echo "[dev1] linux is a great operating system for server." > /dev/winlin1 \ && cat /dev/winlin1 rm -f /dev/winlin* \ && rmmod hello */ module_param(count, int, S_IRUGO|S_IWUSR); module_param(name, charp, S_IRUGO|S_IWUSR); #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/cdev.h> struct winlin_dev{ dev_t devno; char data[1024]; struct cdev cdev; }; const char* msg = "KDD char device buffer.\n"; struct winlin_dev* devs; ssize_t hello_read(struct file* file, char __user* data, size_t size, loff_t* offset ){ struct winlin_dev* dev = file->private_data; int len = strlen(dev->data); if(*offset >= len){ return 0; } printk(KERN_ALERT "hello_read, len=%d, size=%d, offset=%d\n", len, (int)size, (int)*offset); if(copy_to_user(data, dev->data, (len < size)? len:size) != 0){ return -1; } *offset += len; return len; } ssize_t hello_write(struct file* file, const char __user* data, size_t size, loff_t* offset ){ struct winlin_dev* dev = file->private_data; int size_to_write = (sizeof(dev->data) - 1 < size)? sizeof(dev->data) - 1:size; printk(KERN_ALERT "hello_write, len=%d, size=%d, offset=%d\n", size_to_write, (int)size, (int)*offset); if(copy_from_user(dev->data, data, size_to_write) != 0){ return -1; } dev->data[size_to_write] = 0; return size_to_write; } int hello_open(struct inode* node, struct file* file){ struct winlin_dev* dev = container_of(node->i_cdev, struct winlin_dev, cdev); file->private_data = dev; return 0; } int hello_release(struct inode* node, struct file* file){ return 0; } struct file_operations fops = { .owner = THIS_MODULE, .read = hello_read, .write = hello_write, .open = hello_open, .release = hello_release, }; int hello_init(void){ int i; dev_t devno; printk(KERN_INFO "hello, %s!\n", name); if(alloc_chrdev_region(&devno, 0, count, name) != 0){ return -1; } major = MAJOR(devno); devs = kmalloc(count * sizeof(struct winlin_dev), GFP_KERNEL); if(!devs){ return -1; } memset(devs, 0, count * sizeof(struct winlin_dev)); for(i = 0; i < count; i++){ cdev_init(&devs[i].cdev, &fops); devs[i].cdev.owner = THIS_MODULE; //devs[i].cdev.ops = &fops; devs[i].devno = MKDEV(major, i); if(cdev_add(&devs[i].cdev, devs[i].devno, 1) != 0){ return -1; } memcpy(devs[i].data, msg, strlen(msg)); } return 0; } void hello_exit(void){ int i; printk(KERN_ALERT "goodbye, %s\n", name); for(i = 0; i < count; i++){ cdev_del(&devs[i].cdev); } kfree(devs); unregister_chrdev_region(MKDEV(major, 0), count); } module_init(hello_init); module_exit(hello_exit);
Makefile修改为:
ifneq ($(KERNELRELEASE),) obj-m := hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -f *.mod.* *.ko *.order *.symvers *.o install: bash install.sh uninstall: rm -f /dev/winlin* rmmod hello endif
#!/bin/bash #install.sh insmod hello.ko count=2 major_device_number=`cat /proc/devices|grep winlin|awk '{print $1}'` mknod /dev/winlin0 c ${major_device_number} 0 mknod /dev/winlin1 c ${major_device_number} 1