Linux设备驱动第三天(字符设备驱动、cdev)
2016-11-25 22:23
225 查看
设备号的分配
静态分配:
动态分配:
案例 chardevice.文件:
然后编写chardevice:
编译、拷贝到开发板、安装模块;
静态注册、动态注册设备好哪个好?
静态注册优点:简单;驱动加载前已经知道设备好,可以提前创建设备文件
静态注册缺点:容易引起设备号冲突;保留的可用主设备号资源有限;
动态注册优点:简单;便于驱动推广;
动态注册缺点:驱动程序被加载前设备号还没有设备,也就不能为设备提前创造设备文件;
cdev操作步骤与相关方法
cdev案例:
Makefile:
测试程序:在用户空间写一个test.c main,然后调用
将.ko和test.o复制到开发板去,然后安装模块;
然后手工创建一个设备节点文件:
然后执行 ./test
file_operations结构体中经常需要实现的函数
file_operations{
open
release
read
write
ioctrl
mmap
}
我们可以手工添加设备文件:
mknod /dev/abc c250 100
c:字符设备文件
250:主设备文件
100:次设备文件
cdev 参考这里写链接内容
内核空间与用户空间数据交换的问题
copy_to_user 从内核空间向用户空间拷贝
copy_from_user 从用户空间向内核空间拷贝
put_user 往用户空间放数据
get_user 从用户空间取数据
后两个只能完成简单数据的拷贝,如字节、半字、字
前者可以完成批量数据拷贝
以上函数比memcpy多了一个权限校验。
LED案例操作
write(fd,1) //亮灯
write(fd,0) //亮灯
read(fd,buf) //1 亮 0灭
Makefile文件略
test.c文件
静态分配:
动态分配:
/** * 功能:动态申请设备号 * dev:存放返回的设备号的结构体 * firstminor:指定次设备号 * count:连续编号范围 * name:编号相关联的设备名称. (/proc/devices) **/ int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name); /** * 功能:释放设备号 **/ void unregist_chrdev_region(dev_t first,unsigned int count);
案例 chardevice.文件:
#include<linux/init.h> #include<linux/module.h> static int major = 0; static int chardevice_init(void){ dev_t dev; if(major != 0){//静态分配 dev = MKDEV(major,0);//构建一个设备号 //向内核注册设备号 register_chrdev_region(dev,1,"tarena"); }else{ //动态申请注册设备号 alloc_chrdev_region(&dev,100,1,"test"); major = MAJOR(dev);//获取主设备号 相当于dev>>20 unsigned int minor = MINOR(dev);//获取次设备号 相当于把高20位清0 printk("major = %d ! \n",major); } printk("init ! \n"); return 0; } static void chardevice_exit(void){ dev_t dev; dev = MKDEV(major,0); //释放 unregist_chrdev_region(dev,1); } module_init(chardevice_init); module_exit(chardevice_exit); MODULE_LICENSE("GPL");
然后编写chardevice:
obj -m += chardevice.o KDIR = /opt/kernel #指定内核源码路径 all : make -C $(KDIR) SUBDIRS = $(PWD) modules clean: make -C $(KDIR) SUBDIRS = $(PWD) clean
编译、拷贝到开发板、安装模块;
静态注册、动态注册设备好哪个好?
静态注册优点:简单;驱动加载前已经知道设备好,可以提前创建设备文件
静态注册缺点:容易引起设备号冲突;保留的可用主设备号资源有限;
动态注册优点:简单;便于驱动推广;
动态注册缺点:驱动程序被加载前设备号还没有设备,也就不能为设备提前创造设备文件;
cdev操作步骤与相关方法
1. 分配一个cdev,有两种方法: 1),cdev_alloc 2),struct cdev led_cdev 2. 初始化cdev cdev_init(*cdev,*file_operations) 3. 添加cdev到内核 cdev_add(...); 4. 从内核中删除cdev cdev_del(...)
cdev案例:
#include<linux/init.h> #include<linux/module.h> static int led_open(struct inode *inode, struct file *file) { printlk("enter led_open! \n"); return 0; } static int led_close(struct inode *inode, struct file *file) { printlk("enter led_close! \n"); return 0; } static ssize_t led_write (struct file *file, const char __user *buf, size_t count, loff_t *offset) { printlk("enter led_write! \n"); return 0; } static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset) { printlk("enter led_read! \n"); return 0; } static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val) { printlk("enter led_ioctl! \n"); } //需要实现的函数 static struct file_operations led_fops = { .owner = THIS_MOUDLE, .open = led_open, .release = led_close, .write = led_write, .read = led_read, .ioctl = led_ioctl, }; static int major = 0; /*1,分配一个cdev */ static struct cdev led_cdev; static int chardevice_init(void){ dev_t dev; if(major != 0){//静态分配 dev = MKDEV(major,0);//构建一个设备号 //向内核注册设备号 register_chrdev_region(dev,1,"tarena"); }else{ //动态申请注册设备号 alloc_chrdev_region(&dev,100,1,"test"); major = MAJOR(dev);//获取主设备号 相当于dev>>20 unsigned int minor = MINOR(dev);//获取次设备号 相当于把高20位清0 printk("major = %d ! \n",major); } //2, 初始化cdev cdev_init(&led_cdev,&led_fops); //3,把cdev添加到内核里面去 cdev_add(&led_cdev,dev,1); return 0; } static void chardevice_exit(void){ dev_t dev; //4,按照对内核产生的影响,逆序销毁cdev cdev_del(&led_cdev); dev = MKDEV(major,0); //释放 unregist_chrdev_region(dev,1); } module_init(chardevice_init); module_exit(chardevice_exit); MODULE_LICENSE("GPL");
Makefile:
obj -m += chardevice.o KDIR = /opt/kernel #指定内核源码路径 all : make -C $(KDIR) SUBDIRS = $(PWD) modules clean: make -C $(KDIR) SUBDIRS = $(PWD) clean
测试程序:在用户空间写一个test.c main,然后调用
#include<stdio.h> #include<stdlib.h> #include<fcntl.h> int main(void){ int fd; fd = open("/dev/abc",o_RDWR); if(fd<0){ printlf("open /dev/abc failed"); return -1; } char buf[1024] = {0}; int count = 100; read(fd,buf,count);//读取100个字节到buf去 sleep(1); write(fd,buf,count); sleep(1); ioctl(fd,12,34); sleep(1); close(fd); return 0; }
将.ko和test.o复制到开发板去,然后安装模块;
然后手工创建一个设备节点文件:
mknod /dev/abc c 250 #c代表字符设备 250主设备号(250对应模块动态分配的设备号)
然后执行 ./test
file_operations结构体中经常需要实现的函数
file_operations{
open
release
read
write
ioctrl
mmap
}
我们可以手工添加设备文件:
mknod /dev/abc c250 100
c:字符设备文件
250:主设备文件
100:次设备文件
cdev 参考这里写链接内容
内核空间与用户空间数据交换的问题
copy_to_user 从内核空间向用户空间拷贝
copy_from_user 从用户空间向内核空间拷贝
/** *To:用户空间函数 (可以是数组) *From:内核空间函数(可以是数组) *sizeof(from):内核空间要传递的数组的长度 *count: **/ unsigned long copy_to_user(void *to, const void __user *from, usigned long count); unsigned long copy_from_user(void __user *to, const void *from, usigned long count);
put_user 往用户空间放数据
get_user 从用户空间取数据
后两个只能完成简单数据的拷贝,如字节、半字、字
前者可以完成批量数据拷贝
以上函数比memcpy多了一个权限校验。
LED案例操作
write(fd,1) //亮灯
write(fd,0) //亮灯
read(fd,buf) //1 亮 0灭
#include<linux/init.h> #include<linux/module.h> static int led_open(struct inode *inode, struct file *file) { printlk("enter led_open! \n"); return 0; } static int led_close(struct inode *inode, struct file *file) { printlk("enter led_close! \n"); return 0; } //假如在用户空间写入:write(fd,buf,1) buf中的值1亮 0 灭 static ssize_t led_write (struct file *file, const char __user *buf, size_t count, loff_t *offset) { int cmd; copy_from_user(&cmd,buf,sizeof(cmd));//将用户空间的数据拷贝到内核空间 if(cmd == 1){ gpio_set_value(S5PV210_GPC1(3),1);//点亮 led_status = 1; }else{ gpio_set_value(S5PV210_GPC1(3),-); led_status = 0; } return count; } //read(fd,buf,cnt) static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset) { //把灯的状态返回给用户空间 copy_to_user(buf,&led_status,sizeof(led_status)); return count; } static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val) { printlk("enter led_ioctl! \n"); } //需要实现的函数 static struct file_operations led_fops = { .owner = THIS_MOUDLE, .open = led_open, .release = led_close, .write = led_write, .read = led_read, .ioctl = led_ioctl, }; static int major = 0; /*1,分配一个cdev */ static struct cdev led_cdev; static int led_status;//记录灯的状态 static int chardevice_init(void){ dev_t dev; if(major != 0){//静态分配 dev = MKDEV(major,0);//构建一个设备号 //向内核注册设备号 register_chrdev_region(dev,1,"tarena"); }else{ //动态申请注册设备号 alloc_chrdev_region(&dev,100,1,"test"); major = MAJOR(dev);//获取主设备号 相当于dev>>20 unsigned int minor = MINOR(dev);//获取次设备号 相当于把高20位清0 printk("major = %d ! \n",major); } //2, 初始化cdev cdev_init(&led_cdev,&led_fops); //3,把cdev添加到内核里面去 cdev_add(&led_cdev,dev,1); //申请GPIO管脚 gpio_request(S5PV210_GPC1(3),"LED1");//从原图中找管脚名称GPC1_3 //配置为输出 gpio_dirction_output(S5PV210_GPC1(3),0); led_status = 0; return 0; } static void chardevice_exit(void){ dev_t dev; //4,按照对内核产生的影响,逆序销毁cdev cdev_del(&led_cdev); dev = MKDEV(major,0); //释放 unregist_chrdev_region(dev,1); } module_init(chardevice_init); module_exit(chardevice_exit); MODULE_LICENSE("GPL");
Makefile文件略
test.c文件
#include<stdio.h> #include<stdlib.h> #include<fcntl.h> int main(void){ int fd; char buf[1024] = {0}; int count = 100; fd = open("/dev/abc",o_RDWR); if(fd<0){ printlf("open /dev/abc failed"); return -1; } buf[0] =1 ; write(fd,buf,4); sleep(3); read(fd,&(buf[1]),4); if(buf[0] ==1 ){//读到状态为1 printlf("LED ON \n"); }else{ printlf("LED OFF \n"); } close(fd); return 0; }
相关文章推荐
- LDD3《Linux设备驱动》中的最简单的字符设备驱动实现与测试
- linux设备驱动笔记——字符设备驱动
- 字符设备驱动之cdev_init()系列函数
- linux设备驱动第三篇:如何实现一个简单的字符设备驱动
- linux设备驱动第三篇:写一个简单的字符设备驱动
- Linux字符设备驱动之cdev_init
- linux设备驱动--字符设备驱动注册与驱动参数传递
- linux设备驱动第三篇:如何实现一个简单的字符设备驱动
- Linux设备驱动开发详解--笔记6--字符设备驱动
- linux设备驱动第三篇:写一个简单的字符设备驱动
- linux字符设备驱动 cdev
- Linux设备驱动之字符设备驱动
- Linux设备驱动之字符设备驱动
- Linux 字符设备驱动结构(一)—— cdev 结构体、设备号相关知识解析
- Linux设备驱动开发基础---字符设备驱动程序开发之mini2440_LED驱动
- Linux字符设备驱动之cdev_init()
- linux设备驱动第三篇:写一个简单的字符设备驱动
- linux驱动开发--字符设备:通过cdd_cdev结构中的led变量区分是哪个节点,private_data使用
- 字符驱动设备中几个重要的结构体(cdev,file_operations,inode,file)
- Linux字符设备驱动之cdev_init()【十全十美】