您的位置:首页 > 运维架构 > Linux

Linux设备驱动第三天(字符设备驱动、cdev)

2016-11-25 22:23 225 查看
设备号的分配

静态分配:

动态分配:

/**
* 功能:动态申请设备号
* 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: