Linux驱动学习-----最简单的Hello程序

简介: Linux驱动学习-----最简单的Hello程序

1. 写驱动的步骤

1.1 确定主设备号

一般都是直接让系统自己指定

  static int major = 0;  // 0就表示让系统自己选一个未使用的设备号

1.2 实现驱动层的read,write,open,close,ioctl等等函数

这些函数不需要全部实现,依据驱动需求实现需要用到的函数。这些函数是一些内核层次的函数。和内核提供的接口(read, write, open, close, ioctl等等)是有关系的,内核提供的这些函数会调用这些函数。

static ssize_t hello_drv_read(struct file * file, char __user * buf, size_t size, loff_t *offset)
{
  // 读取设备对应的寄存器的值,并处理后将结果通过buf返回给上层函数open
  ...
}
static ssize_t hello_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
  // 目前还没用到该函数  
  ...
}
static int hello_drv_open(struct inode *node, struct file *file)
{
  // 初始化硬件操作,配置对应的寄存器, 比如开时钟,设置GPIO模式等等操作
  ...
}
static int hello_drv_close(struct inode *node, struct file *file)
{
  // 关闭硬件设备,设置对应的寄存器
  ...
}

1.3 定义file_operation结构体

结构体,release函数对应于close函数,在关闭文件时,系统调用close函数中就会调用该release函数


68874decd9794217b94b91c35c19d70f.png

将1.2中的函数绑定到该结构体内

  static struct file_operations hello_drv = {
  .owner = THIS_MODULE,  // 使用".成员变量"的赋值是gcc的用法
  .open = hello_drv_open,
  .read = hello_drv_read,
  .write = hello_drv_write,
  .release = hello_drv_close,
};

1.4 注册驱动函数

  major = register_chrdev(0, "hello", &hello_drv);  // 返回值就是系统分配的设备号

1.5 设置入口函数

驱动在挂载的命令就是"insmod xxx.ko", 而insmod就会调用该函数,完成驱动的注册

static int __init hello_init(void)
{
  // 1. 注册驱动
  // 2. 添加设备节点
}
module_init(hello_init);       // insmod

1.6 设置出口函数

使用rmmod会调用该函数

static void __exit hello_exit(void)
{
  // 删除设备节点
  // 取消驱动注册
}
module_exit(hello_exit);       // rmmod

1.7 添加设备节点

hello_class = class_create(THIS_MODULE, "hello_class");
err = PTR_ERR(hello_class);
if (IS_ERR(hello_class)) {
  unregister_chrdev(major, "hello");
  return -1;
}
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");  // /dev/hello

2. 实例代码

驱动程序简单地开辟一个字符串数组,在应用层程序写数据时存放在这个数组内,读数据时将该数组内的数据返回。

2.1 驱动程序代码

hello_drv.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
// 1. 确定主设备号
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;
#define MIN(a,b) (a<b?a:b)
// 3. 定义自己的read,write,open,release函数
static ssize_t hello_drv_read(struct file * file, char __user * buf, size_t size, loff_t *offset)
{
  int err;
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  err = copy_to_user(buf, kernel_buf, MIN(size,1024));
  return MIN(size, 1024);
}
static ssize_t hello_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
  int err;
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  err = copy_from_user(kernel_buf, buf, MIN(size,1024));
  return MIN(size, 1024);
}
static int hello_drv_open(struct inode *node, struct file *file)
{
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  return 0;
}
static int hello_drv_close(struct inode *node, struct file *file)
{
  printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  return 0;
}
// 2. 定义自己的file_operations结构体
static struct file_operations hello_drv = {
  .owner = THIS_MODULE,  //gcc用法
  .open = hello_drv_open,
  .read = hello_drv_read,
  .write = hello_drv_write,
  .release = hello_drv_close,
};
// 4. 注册驱动函数,将file_operation结构体告诉内核
// 5. 注册驱动函数需要一个入口函数,在安装驱动的时候回去调用该函数,从而完成注册
static int __init hello_init(void)
{
  int err;
  major = register_chrdev(0, "hello", &hello_drv);
  hello_class = class_create(THIS_MODULE, "hello_class");
  err = PTR_ERR(hello_class);
  if (IS_ERR(hello_class)) {
    unregister_chrdev(major, "hello");
    return -1;
  }
  device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");  // /dev/hello
  return 0;
}
// 6. 出口函数,卸载驱动的时候会调用该函数
static void __exit hello_exit(void)
{
  device_destroy(hello_class, MKDEV(major, 0));
  class_destroy(hello_class);
  unregister_chrdev(major, "hello");
}
// 7. 提供设备信息,自动创建设备节点
module_init(hello_init);       // insmod
module_exit(hello_exit);       // rmmod
MODULE_LICENSE("GPL");  // 这个必须要有

2.2 应用测试程序代码

hello_drv_test.c

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<string.h>
int main(int argc, char **argv)
{
  int fd;
  int ret;
  char buf[1024];
  if (argc < 2) {
    printf("Usage: %s -w <string>\n", argv[0]);
    printf("       %s -r\n", argv[0]);
    return -1;
  }
  fd = open("/dev/hello", O_RDWR);
  if (fd == -1) {
    perror("open error");
    return -1;
  }
  // 写入数据
  if (strcmp(argv[1], "-w") == 0) {
    if (argc < 3) {
      printf("Usage: %s -w string\n", argv[0]);
      printf("       %s -r\n", argv[0]);
      return -1;
    }
    if (write(fd, argv[2], strlen(argv[2])) == -1) {
      perror("write error");
      return -1;
    }
    printf("write successful\n");
  }
  // 读出数据
  if (strcmp(argv[1], "-r") == 0) {
    ret = read(fd, buf, 1024);
    if (ret == -1) {
      perror("read error");
      return -1;
    }
    else if (ret > 0) {
      printf("data: %s\n", buf);
    }
    printf("read successful\n");
  }
  close(fd);
  return 0;
}

2.3 Makefile

ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export  ARCH  CROSS_COMPILE
KERN_DIR = /home/hxd/workdir/ebf_linux_kernel_6ull_depth1/build_image/build   # linux内核目录,驱动程序是需要内核的一些头文件的
all:
  make -C $(KERN_DIR) M=`pwd` modules 
  $(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c 
clean:
  make -C $(KERN_DIR) M=`pwd` modules clean
  rm -rf modules.order
  rm -f hello_drv_test
obj-m += hello_drv.o

2.4 运行

  • 执行make编译好后,将hello_drv_test程序文件和hello_drv.ko驱动文件拷贝到板子上,
  • 使用命令insmod hello_drv.ko进行驱动的挂载,挂载好后可发现/dev目录下多了个hello字符设备文件
  • 执行hello_drv_test程序

3. 遇到的问题

3.1 makefile编译时的所使用的linux版本要和板子上的系统版本一致,不然会运行不了







目录
相关文章
|
11天前
|
Ubuntu Linux vr&ar
IM跨平台技术学习(十二):万字长文详解QQ Linux端实时音视频背后的跨平台实践
本文详细记录了新版QQ音视频通话在 Linux 平台适配开发过程中的技术方案与实现细节,希望能帮助大家理解在 Linux 平台从 0 到 1 实现音视频通话能力的过程。
32 2
|
2天前
|
Ubuntu 应用服务中间件 Linux
Linux学习之Ubuntu 20中OpenResty的nginx目录里内容和配置文件
总的来说,OpenResty的Nginx配置文件是一个强大的工具,它允许你以非常灵活的方式定义你的Web服务的行为。
8 2
|
10天前
|
安全 小程序 Linux
Linux中信号是什么?Ctrl + c后到底为什么会中断程序?
信号在进程的学习中是一个非常好用的存在,它是软件层次上对中断机制的一种模拟,是异步通信方式,同时也可以用来检测用户空间到底发生了什么情况,然后系统知道后就可以做出相应的对策。
|
10天前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
18天前
|
安全 Linux
蓝易云 - Linux学习之RAID
以上就是Linux中RAID的基本概念和使用方法。
16 1
|
20天前
|
安全 Linux
蓝易云 - Linux学习之RAID
最后,你可以使用 `mdadm --detail /dev/md0`命令检查RAID状态。
19 2
|
23天前
|
安全 物联网 Linux
学习Linux对网络安全的重要性
**学习Linux对网络安全至关重要:** 1. 开源操作系统广泛应用于服务器、网络设备,掌握Linux是安全专家必备技能。 2. Linux内置安全特性,如最小权限和防火墙,加上丰富的安全工具,提供强大保障。 3. 可定制性允许灵活配置,满足安全需求,开源社区提供持续更新和教育资源。 4. 学习Linux能提升攻防能力,用于系统加固和渗透测试,适应跨平台安全场景。 5. 随着云计算和物联网发展,Linux在网络安全中的角色日益关键。
48 3
|
28天前
|
Linux
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
|
28天前
|
Linux 程序员 芯片
【Linux驱动】普通字符设备驱动程序框架
【Linux驱动】普通字符设备驱动程序框架
|
10天前
|
运维 监控 大数据
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署