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

设备号的分配
静态分配:

动态分配:

/**
* 功能:动态申请设备号
* 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,1001,"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,1001,"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,1001,"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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值