设备三种类型
1、字符设备
2、块设备
3、网络设备
字符设备:在IO传输过程中以字符为单位,传输速率较慢
内核代码:demo_chr_dev.c
1 #include <linux/module.h>//define THIS_MODULE 2 #include <linux/kernel.h> 3 #include <linux/fs.h> //define file_operations 4 #include <linux/cdev.h>//define cdev 5 6 static struct cdev chr_dev; 7 static dev_t ndev;//char device main id and sub id 8 9 static int chr_open(struct inode * nd, struct file *filp) 10 { 11 int major = MAJOR(nd->i_rdev); 12 int minor = MINOR(nd->i_rdev); 13 printk("char_open, major=%d,minor=%d ", major, minor); 14 return 0; 15 }; 16 17 static ssize_t chr_read(struct file *f, char __user *u, size_t sz, loff_t *off) 18 { 19 printk("In the char_read() function! "); 20 return 0; 21 } 22 23 struct file_operations chr_ops = 24 { 25 .owner = THIS_MODULE, 26 .open = chr_open, 27 .read = chr_read, 28 }; 29 30 31 static int demo_init(void) 32 { 33 int ret; 34 cdev_init(&chr_dev, &chr_ops); 35 ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); 36 if(ret < 0) 37 return ret; 38 printk("demo_init(): major=%d,minor=%d ", MAJOR(ndev), MINOR(ndev)); 39 ret = cdev_add(&chr_dev, ndev, 1); 40 if(ret < 0) 41 return ret; 42 return 0; 43 } 44 45 static void demo_exit(void) 46 { 47 printk("Removing char_dev module... "); 48 cdev_del(&chr_dev); 49 unregister_chrdev_region(ndev, 1); 50 } 51 52 module_init(demo_init); 53 module_exit(demo_exit); 54 55 56 MODULE_LICENSE("GPL"); 57 MODULE_AUTHOR("Sain"); 58 MODULE_DESCRIPTION("a char device driver as an example");
//Makefile
ifeq ($(KERNELRELEASE),)
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
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules*
.PHONY: modules modules_install clean
else
obj-m := demo_chr_dev.o
endif
整个代码相较于hello world 主要增加了下面两个响应函数
.open = chr_open,
.read = chr_read,
动态加载内核模块
sudo insmod demo_chr_dev.ko
dmesg能看到打出来相应日志了:
[ 4833.212757] demo_init(): major=245,minor=0
驱动测试程序
1 //main.c 2 3 4 #include <stdio.h> 5 #include <fcntl.h> //define O_RDONLY 6 #include <unistd.h> //use posix function open read 7 #define CHR_DEV "/dev/chr_dev" 8 int main(void){ 9 10 int ret; 11 char buff[32]={0}; 12 int fd = open(CHR_DEV, O_RDONLY | O_NDELAY); 13 if(fd < 0){ 14 printf("open file %s failed ", CHR_DEV); 15 return -1; 16 } 17 18 read(fd,buff,32); 19 printf("read:[%s]", buff); 20 close(fd); 21 return 0; 22 23 }
gcc main.c -o main( 注意:不要gcc -c main.c -o main,加上-c完全时另外一个意思了)
比较坑的是,直接执行./main 函数会提示打开失败
还要根据设备号信息用mknod 命令在系统的/dev/目录创建新的设备节点
ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev");
根据前面的log创建设备节点sudo mknod chr_dev c 245 0
ninjame@ubuntu1604:/dev$ ls -l chr_dev crw-r--r-- 1 root root 245, 2 9月 1 00:13 chr_dev
重新执行./main
可以看到应用程序成功调用到了设备驱动程序实现的函数
[ 4833.212757] demo_init(): major=245,minor=0
[ 5069.412260] char_open, major=245,minor=0
[ 5069.412269] In the char_read() function!
ps:
1、实际中发现创建节点的名字可以随意 ,只要主设备号、次设备号对上就可以
sudo mknod chr_devmode c 245 0 也是可以的
2、测试代码open 函数在打开失败时是完全不会调到内核函数的
chr_open 函数不存在出错分支,可见内核的调用和我们普通调用函数还是有很大区别的
static int chr_open(struct inode * nd, struct file *filp) { int major = MAJOR(nd->i_rdev); int minor = MINOR(nd->i_rdev); printk("char_open, major=%d,minor=%d ", major, minor); return 0; };