哈尔滨理工大学软件工程专业08-7李万鹏原创作品,转载请标明出处
http://blog.csdn.net/woshixingaaa/archive/2010/11/13/6007342.aspx
LINUX设备驱动程序分为字符设备驱动(无缓冲且只能顺序存取),块设备驱动程序(有缓冲且可以随机存取)。每个字符设备和块设备都必须有主,次设备号,主设备号相同的设备是同类设备(使用同一驱动程序)。这些设备中,有些设备是对实际物理硬件的抽象,而有些设备则是则是内核自身提供的功能(不依赖于特定的物理硬件,又称为“虚拟设备”)。每个设备在/dev目录下都有一个对应的文件(节点),可以通过 cat /proc/devices命令查看当前已经加载的设备驱动程序的主设备号。
在内核中,dev_t类型用来保存设备编号(包括主设备号和次设备号):
- 主设备号=MAJOR(dev_t dev)
- 次设备号=MINOR(dev_t dev)
- 设备编号=MKDEV(int major,int minor)
dev_t是无符长整型
- typedef u_long dev_t ;
- typedef unsigned long u_long;
分配和释放设备号:
- 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);
字符设备的注册:
- void cdev_init(struct cdev *cdev, struct file_operations *fops);
- int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
- void cdev_del(struct cdev *dev);
早期的办法:
- int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
- int unregister_chrdev(unsigned int major, const char *name);
在内核空间和用户空间之间拷贝数据使用:
- unsigned long copy_to_user(void __user*to,const void *from,unsigned long count);
- unsigned long copy_from_user(void *to, const void __user *from,unsigned long count);
scull设备的布局:
这个结构是一个二维数组,qset是第一个下限,quantum是第二个下限。
下面是我写的驱动程序,没有用书上的,数了一下,书上的例子快到两千行代码了,所以自己写个容易的,设备是内存,first.c 需要注意的地方是,下面的struct file_operations first_fops是用C99标准写的,是标记化结构体初始化语法。左边的open,release,read,write都是系统调用,也就是说调用read是去执行read_test,调用write时执行write_test。在用户空间和内核空间之间传输数据应该使用函数copy_to_user和copy_from_user.
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/init.h>
- #include <linux/miscdevice.h>
- #include <linux/sched.h>
- #include <linux/delay.h>
- #include <linux/poll.h>
- #include <linux/spinlock.h>
- #include <linux/cdev.h>
- #include <linux/kobject.h>
- #include <linux/fs.h>
- #include <linux/device.h>
- #include <asm/uaccess.h>
- struct cdev first;
- unsigned int dev;
- char * data;
- static ssize_t read_test(struct file *file, char *buf, size_t count,loff_t*pos);
- static ssize_t write_test(struct file *file, const char *buf,size_t count,loff_t*pos);
- static ssize_t open_test(struct inode *inode, struct file *file);
- static ssize_t release_test(struct inode *inode, struct file *file);
- struct file_operations first_fops={
- .owner = THIS_MODULE,
- .read = read_test,
- .write = write_test,
- .open = open_test,
- .release = release_test,
- };
- static ssize_t read_test(struct file *file, char *buf, size_t count,loff_t*pos){
- int len;
- if(count < 0)
- return -EINVAL;
- len = strlen(data);
- if(len < count)
- count = len;
- copy_to_user(buf,data,count);
- printk("read buf %s/n", buf);
- printk("read data %s/n", data);
- return count;
- }
- static ssize_t write_test(struct file *file, const char *buf,size_t count,loff_t*pos){
- if(count < 0)
- return -EINVAL;
- kfree(data);
- data = (char*)kmalloc(sizeof(char)*(count),GFP_KERNEL);
- if(!data)
- return -ENOMEM;
- copy_from_user(data,buf,count);
- printk("write buf %s/n",buf);
- printk("write data %s/n",data);
- return count;
- }
- static ssize_t open_test(struct inode *inode, struct file *file){
- return 0;
- }
- static ssize_t release_test(struct inode *inode, struct file *file){
- return 0;
- }
- static int __init init_first(void){
- int ret;
- ret = alloc_chrdev_region(&dev,0,1,"first");
- if(ret < 0){
- printk(KERN_ALERT "first register fail");
- }
- cdev_init(&first,&first_fops);
- first.owner = THIS_MODULE;
- ret = cdev_add(&first,dev,1);
- if(ret < 0){
- printk(KERN_ALERT "first registrr fail");
- }
- return 0;
- }
- static void __exit exit_first(void){
- unregister_chrdev_region(dev,"first");
- cdev_del(&first);
- }
- module_init(init_first);
- module_exit(exit_first);
初始化模块时:
- 获得设备号 alloc_chrdev_region,若已知则调用register_chrdev_region
- 初始化字符设备 cdev_init
- 将设备添加到系统中 cdev_add
注销模块:
- 释放设备号 unregister_chrdev_region
- 删除已经注册的char设备 cdev_del
Makefile
- ifneq ($(KERNELRELEASE),)
- obj-m:= first.o
- else
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- PWD := $(shell pwd)
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- modules_install:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
- clear:
- rm -rf *.o
- .PHONY: modules modules_install clear
- endif
首先make,进行编译,生成first.ko文件。执行:
- sudo insmod first.ko
- cat /proc/devices 查看主设备号
- sudo mknod /dev/test c 252 0 (c是字符设备的意思,250是我查看得到的主设备号,0是次设备号)
test.c
- #include <stdio.h>
- #include <fcntl.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- int main(){
- int fd;
- char s2[10]="0";
- char *s1 = "hacker";
- fd = open("/dev/test",O_RDWR);
- if(fd == -1){
- printf("Cannot open first/n");
- exit(0);
- }
- write(fd,s1,strlen(s1));
- read(fd,s2,strlen(s1));
- printf("显示出字符串:/n");
- printf("%s/n",s2);
- close(fd);
- return 0;
- }
- 然后编译用户空间的测试文件test.c 运行