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版本要和板子上的系统版本一致,不然会运行不了







目录
相关文章
|
27天前
|
Linux 编译器 开发工具
【Linux快速入门(三)】Linux与ROS学习之编译基础(Cmake编译)
【Linux快速入门(三)】Linux与ROS学习之编译基础(Cmake编译)
|
1月前
|
存储 安全 Linux
|
3月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
205 6
|
4月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
704 2
|
4月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
72 2
|
1月前
|
Linux Shell 数据安全/隐私保护
|
2月前
|
Linux 编译器 C语言
【Linux快速入门(一)】Linux与ROS学习之编译基础(gcc编译)
【Linux快速入门(一)】Linux与ROS学习之编译基础(gcc编译)
|
2月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
62 1
|
2月前
|
网络协议 Linux
linux学习之套接字通信
Linux中的套接字通信是网络编程的核心,允许多个进程通过网络交换数据。套接字提供跨网络通信能力,涵盖本地进程间通信及远程通信。主要基于TCP和UDP两种模型:TCP面向连接且可靠,适用于文件传输等高可靠性需求;UDP无连接且速度快,适合实时音视频通信等低延迟场景。通过创建、绑定、监听及读写操作,可以在Linux环境下轻松实现这两种通信模型。
43 1
|
3月前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
48 5