迅为嵌入式linux驱动开发笔记(一)—杂项设备、应用与内核间数据传输

一、Linux下第一个驱动

驱动分为四个部分:
头文件
驱动模块的入口和出口
声明信息
功能实现
第一步 包含头文件

1 #include<linux/init.h>
	包含宏定义的头文件
2 #include<linux/module.h>
	包含初始化加载模块的头文件

第二步,驱动模块的入口和出口

module_init();

module_exit();

第三步 声明模块拥有开源许可证

MODULE_LICENSE("GPL");

第四步 功能的实现

内核加载时打印 printk(“hello world”);

static init hello_init(void)
{
    printk("hello world");
    return 0;
}

内核卸载时打印 printk(“bye bye”);

static init hello_exit(void)
{
    printk("bye bye");
    return 0;
}
驱动代码编写:
#include<linux/init.h>
#include<linux/module.h>

static init hello_init(void)
{
    printk("hello world");
    return 0;
}

static init hello_exit(void)
{
    printk("bye bye");
    return 0;
}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE("GPL");

二、Linux下驱动模块编译讲解

第一种方法:
把驱动编译成模块,然后使用命令把驱动加载到内核里面

第二种方法:
直接把驱动编译到内核。

编译成模块

第一步先写makefile

obj-m +=helloworld.o

KDIR:=/home/q123/Desktop/linux/elinux

PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(pwd) modules

第二步:编译驱动
编译驱动之前需要注意的问题:
1、内核源码一点要先编译通过
2、编译驱动模块用的内核源码一定要和开发板上的运行的镜像是同一套。
3、看一下ubuntu的环境是不是arm的。
在这里插入图片描述
设置环境变量
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
编译通过可喜可贺!在这里插入图片描述编译成功之后.ko文件就是我们编译好的驱动文件了。

加载驱动模块

insmod helloworld.ko

卸载驱动模块

rmmod helloworld 没有ko的后缀名字

实验现象

将生成的驱动模块拷贝到ununtu的挂载的nfs文件目录下,加载驱动文件,加载时打印 hello world,卸载时打印 bye bye
在这里插入图片描述

三、make menuconfig图形配置界面

1、进入到内核源码路径下,然后输入make menuconfig即可打开此界面。
2、make menuconfig图形化界面的操作
1)搜索功能
输入“/”即可弹出搜索界面,然后输入需要搜索的内容即可。
2)配置驱动的状态
(1)把驱动编译成模块,用M表示。
(2)把驱动编译到内核中,用*表示。
(3)不编译
3、退出
退出分为保存退出和不保存退出

4、make menuconfig有关的文件
Makefile
里面是编译规则。菜的做法。
Kconfig
内核配置的选项,相当于饭店吃饭服务员给的菜单
.config
配置完成内核之后生成的配置选项。相当于点完的菜。

5、make menuconfig会读取哪个目录下的Kconfig文件arch/$ARCH/目录下的Kconfig

/arch/arm/configs#下面有好多配置文件,相当于饭点的特色菜。

6、为什么要复制成.config而不复制成其他文件呢?
肯定不行,因为内核默认读取的Linux内核根目录下的.config作为默认配置选项的。

7、复制的.config不完全符合要求怎么办?
需要使用make menuconfig点菜,调出菜单。配置完成之后自己更新到.config中

8、怎么和Makefile文件建立关系?
当make menuconfig保存退出之后,Linux会将所以的配置选项以宏定义的形式保存在include/generated下面的autoconf.h中。

四、Linux三大设备驱动

字符设备:IO的传输过程是以字符为单位的,没有缓冲。比如IIC,SPI都是字符设备。

块设备:IO的传输过程都是以块为单位的。跟存储相关的都是属于块设备,比如,tf卡

网络设备:与前面两个不一样,是以socket套接字来访问的。

1、杂项设备驱动

杂项设备驱动是字符设备的一种,可以自动生成设备结点。

系统中有许多杂项设备。
cat /proc/misc命令来查看。

2、杂项设备除了比字符设备代码简单,还有什么区别吗?
杂项设备的主设备号是相同的,均为10,次设备号是不同的,主设备号相同就可以节省内核的资源。

3、主设备号和次设备号是什么?

主设备号包含次设备号,主设备号在linux中是唯一的,次设备号不一定唯一。

设备号是计算机识别设备的一种方式,主设备相同的就被视为同一类设备。

主设备号可以必做成电话号码的区号,比如安徽是0551。

次设备号可以比作电话号码。

主设备号可以通过命令cat /proc/devices来查看。

4、杂项设备驱动描述
定义在内核源码路径: vi include/linux/miscdevice.h

  struct miscdevice  {
      int minor;//次设备号
     const char *name;//设备节点的名字
      const struct file_operations *fops;//文件操作集
      struct list_head list;
      struct device *parent;
      struct device *this_device;
      const struct attribute_group **groups;
      const char *nodename;
      umode_t mode;
  };
  

file_operations *fops;//文件操作集在vi include/linux/fs.h下。
在这里插入图片描述一个结构体成员都要对应一个调用。

注册杂项设备

extern int misc_register(struct miscdevice *misc);

注销杂项设备

extern int misc_deregister(struct miscdevice *misc);

5 注册杂项设备的流程。
(1)填充miscdevice这个结构体的成员。
(2)填充file_operation这个结构体。
(3)注册杂项设备并且生成设备节点。
自主分配设备号:
在这里插入图片描述

五、实践课

目标:按照杂项设备的注册流程注册一个杂项设备,并且生成设备节点。

创建misc.c的源文件

#include<linux/init.h>
#include<linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

struct file_operations  misc_fops =
{
    .owner = THIS_MODULE
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops
};

static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);//注册
    if (ret<0)
    {
        printk("misc_register is error\n");
        return -1;
    }
    printk("misc_register is successful\n");
    return 0;
}
static int misc_exit(void)
{
    misc_deregister(&misc_dev);//卸载
    printk("bye bye");
    return 0;
}
module_init(misc_init);

module_exit(misc_exit);

MODULE_LICENSE("GPL");

在make之后,将ko文件挂载到nfs下,使用insmod挂载设备驱动。

应用层和内核层的数据传输

Linux一切皆文件!!!

文件对应的操作有打开,关闭,读写。

设备结点对应的操作有打开,关闭,读写。

1、如果在应用层使用系统IO对设备结点进行打开,关闭,读写操作等会发生什么呢?

当在应用层read设备节点的时候,就会触发驱动里面的read这个函数。
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

当在应用层write设备节点的时候,就会触发驱动里面的write这个函数。
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

当在应用层poll/select设备节点的时候,就会触发驱动里面的poll这个函数。
unsigned int (*poll) (struct file *, struct poll_table_struct *);

当在应用层ioctl设备节点的时候,就会触发驱动里面的ioctl这个函数。
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

当在应用层open设备节点的时候,就会触发驱动里面的open这个函数。
int (*open) (struct inode *, struct file *);

当在应用层close设备节点的时候,就会触发驱动里面的close这个函数。
int (*release) (struct inode *, struct file *);

open、release、read、write驱动代码测试

在file_operation结构体中添加如下代码,make完成。

#include<linux/init.h>
#include<linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

int misc_open(struct inode *inode,struct file *file)
{
    printk("hello misc_open\n");

    return 0;
}
int misc_close(struct inode *inode,struct file *file)
{
    printk("bye bye\n");

    return 0;
}

int misc_read(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
{
    char kbuf[64] = "hello";

    if(copy_to_user(ubuf,kbuf,sizeof(kbuf))!=0)
    {
        printk("copy to user error\n");
        return -1;
    }  
    return 0;
}

int misc_write(struct file *file,const char __user *ubuf,size_t size,loff_t *loff_t)
{
   char kbuf[64] = {0};

    if(copy_from_user(kbuf,ubuf,size)!=0)
    {
        printk("copy_from_user\n");
        return -1;
    }  
    printk("kbuf is %s\n",kbuf);
    
    return 0;
}

struct file_operations  misc_fops ={
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_close,
    .read = misc_read,
    .write = misc_write
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops
};

static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);//注册
    if (ret<0)
    {
        printk("misc_register is error\n");
        return -1;
    }
    printk("misc_register is successful\n");
    return 0;
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);//卸载
    printk("bye bye");
}

module_init(misc_init);

module_exit(misc_exit);

MODULE_LICENSE("GPL");

open、close、read、write应用代码测试

应用层代码如下

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd;
    char buf[64];
    fd = open("/dev/hello_misc",O_RDWR);
    if (fd < 0) 
    {
        printf("open error\n");
        return fd;
    }
    read(fd, buf, sizeof(buf));
    
    write(fd, buf, sizeof(buf));
    
    close(fd);
    
    return 0;
}

使用交叉编译器编译应用层代码

arm-linux-gnueabihf-gcc app.c -o app -static

复制到nfs目录下调用./app实现对驱动代码的调用~~!!!

实验现象

首先insmod加载驱动代码:之后运行./app,打印数据信息,
在这里插入图片描述
将kbuf中的数据打印出来。
在这里插入图片描述

六、小结

1、调用关系

上层应用 设备节点 底层驱动

设备节点就是连接上层应用和底层的桥梁。

2、假如file_operations里面没有read,在应用层read设备节点会发生什么?

什么也不会发生,也不会报错。

3、应用层和内核层不能直接进行数据传输的。

打开include/asm-generic

copy_from_user 用户层向内核层传递数据。

static inline long copy_from_user(void *to,const void __user * from, unsigned long n)
 {
     might_fault();
     if (access_ok(VERIFY_READ, from, n))
         return __copy_from_user(to, from, n);
     else
         return n;
 }

copy_to_user 内核层向应用层传递数据。

 static inline long copy_to_user(void __user *to,
         const void *from, unsigned long n)
 {
     might_fault();
     if (access_ok(VERIFY_WRITE, to, n))
         return __copy_to_user(to, from, n);
     else
         return n;
 }

copy_to_user测试
包含头文件

#include <linux/uaccess.h>

代码

int misc_read(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
{
    char kbuf[64] = "hello";

    if(copy_to_user(ubuf,kbuf,sizeof(kbuf))!=0)
    {
        printk("copy to user error\n");
        return -1;
    }  
    return 0;

}

copy_from_user测试

int misc_write(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
{
   char kbuf[64] = {0};

    if(copy_from_user(kbuf,ubuf,size)!=0)
    {
        printk("copy_from_user\n");
        return -1;
    }  
    printk("kbuf is %s\n",kbuf);
    return 0;

}
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值