深入解析:Linux 驱动

1.1 详见 Linux C编程-CSDN博客

1.2 Linux 驱动

1.2.1 我的第一个Linux驱动

  • 查看linux环境中的fs.h文件
cd /
sudo find -name "fs.h"

可以看到如下,一般是在./usr/src/linux-hwe-6.8-headers-6.8.0-87/include/linux/fs.h这个路径下(实际源码路径无查看权限,但是这个路径可以查看),通过vim即可查看。

xiaobai@xiaobai:/$ sudo find -name "fs.h"
find: ‘./run/user/1000/gvfs’: 权限不够
find: ‘./run/user/1000/doc’: 权限不够
find: ‘./run/user/128/doc’: 权限不够
./usr/src/linux-hwe-6.8-headers-6.8.0-87/include/trace/misc/fs.h
./usr/src/linux-hwe-6.8-headers-6.8.0-87/include/linux/fs.h
./usr/src/linux-hwe-6.8-headers-6.8.0-87/include/linux/mlx5/fs.h
./usr/src/linux-hwe-6.8-headers-6.8.0-87/include/uapi/linux/fs.h
./usr/src/linux-hwe-6.8-headers-6.8.0-86/include/trace/misc/fs.h
./usr/src/linux-hwe-6.8-headers-6.8.0-86/include/linux/fs.h
./usr/src/linux-hwe-6.8-headers-6.8.0-86/include/linux/mlx5/fs.h
./usr/src/linux-hwe-6.8-headers-6.8.0-86/include/uapi/linux/fs.h

图示如下:

  • 首先找到实际源码路径,如下
xiaobai@xiaobai:~/linux/Linux_Drivers/1_chrdevbase$ uname -r
6.8.0-87-generic

然后在./usr/src该目录下可以找到一个如下几个文件

xiaobai@xiaobai:/usr/src/linux-hwe-6.8-headers-6.8.0-87$ cd ..
xiaobai@xiaobai:/usr/src$ ll
总计 28
drwxr-xr-x  7 root root 4096 11月  5 00:00 ./
drwxr-xr-x 14 root root 4096  9月 11  2024 ../
drwxr-xr-x  7 root root 4096 10月 29 11:44 linux-headers-6.8.0-86-generic/
drwxr-xr-x  7 root root 4096 11月  4 10:35 linux-headers-6.8.0-87-generic/
drwxr-xr-x 26 root root 4096 10月 29 11:44 linux-hwe-6.8-headers-6.8.0-86/
drwxr-xr-x 26 root root 4096 11月  4 10:35 linux-hwe-6.8-headers-6.8.0-87/
drwxr-xr-x  4 root root 4096 11月  5 00:00 python3.10/

其中这个linux-headers-6.8.0-87-generic就是所在源码文件夹。然后新建一个.vscode文件夹新建两个文件分别是c_cpp_properties.jsonsettings.json 里面内容如下

c_cpp_properties.json

{
    "configurations": [
        {
            "name": "linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/src/linux-headers-6.8.0-87-generic/include",
                "/usr/src/linux-headers-6.8.0-87-generic/arch/arm/include"
            ],
            "intelliSenseMode": "linux-gcc-x64",
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "gnu++17"
        }
    ],
    "version": 4
}

注意

 "/usr/src/linux-headers-6.8.0-87-generic/include",
 "/usr/src/linux-headers-6.8.0-87-generic/arch/arm/include"

这两行代码需要换成你自己的源文件所在目录当然./usr/src/linux-hwe-6.8-headers-6.8.0-87这个目录也可以尝试一下。

settings.json

{
    "files.associations": {
        "*.py": "python",
        "module.h": "c"
    },
    "search.exclude": {
        "**/node_modules": true,
        "**/bower_components": true,
        "**/*.code-search": true,
        "**/*.o":true,
        "**/*.su":true,
        "**/*.cmd":true,
        "Documentation":true
    },
    "files.exclude": {
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/.DS_Store": true,
        "**/Thumbs.db": true,
        "**/CVS":true,
        "**/*.o":true,
        "**/*.su":true,
        "**/*.cmd":true,
        "Documentation":true
    }
}

在.vscode同级文件夹下建立chardevbase.c文件如下

#include 
MODULE_LICENSE("GPL");
static int __init chardevbase_init(void)
{
    return 0;
}
static void __exit chardevbase_exit(void)
{
}
/*
    模块入口与出口
*/
module_init(chardevbase_init); /* 入口 */
module_exit(chardevbase_exit);/* 出口 */

再写一个Makefile,注意这个KERNELDIR必须是你的实际源码所在位置。

KERNELDIR := /usr/src/linux-headers-6.8.0-87-generic
CURRENT_PATH := $(shell pwd)
obj-m := chardevbase.o
build: kernel_modules
kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

创建.ko文件

make

通过如下命令进行驱动的加载与移除

sudo insmod chardevbase.ko
sudo rmmod chardevbase

或者

sudo modprode chardevbase.ko
sudo modprode -r chardevbase

第一个加载第二个删除并且可以通过lsmod | grep chardevbase查看是否加载成功。

注意使用insmod并不能自动查找驱动之间的依赖关系,但是使用modprode可以。

例如如果你有个驱动叫 mc.ko依赖lc.ko如果你要成功使用mc.ko则需要先用insmod加载lc.ko再加载mc.ko但如果你用modprode则可以直接使用modprode mc.ko ,但需要你自己把.ko移到/lib/modules/6.8.0-87-generic目录下,注意首次使用时需要运行sudo depmod。否则还是会报错modprobe: FATAL: Module chardevbase not found in directory /lib/modules/6.8.0-87-generic

  • 实现驱动加载的调试,打印相关输出!!!(注意输出难以通过打印台打印出来需要通过sudo dmesg | tail -8查看)

修改chardevbase.c

#include 
#include 
static int __init chardevbase_init(void)
{
    printk("driver init!!!!\n");
    return 0;
}
static void __exit chardevbase_exit(void)
{
    printk(KERN_EMERG "chardevbase: module unloaded\n");
}
/*
    模块入口与出口
*/
module_init(chardevbase_init); /* 入口 */
module_exit(chardevbase_exit);/* 出口 */
MODULE_LICENSE("GPL");

输出如下

xiaobai@xiaobai:/usr/src/linux-hwe-6.8-headers-6.8.0-87$ sudo dmesg | tail -8
[52454.452524] driver init!!!!
[52543.248780] chardevbase: module unloaded
    • 嵌入式设备的注册与注销

首先可以看到major对应的主设备号,extern int register_chrdev_region(dev_t, unsigned, const char *);这个代码中的dev_t代表设备号,typedef __kernel_dev_t dev_t;再去索引__kernel_dev_t发现typedef u32 __kernel_dev_t;u32再去索引typedef __u32 u32;发现是__u32最终得出是typedef unsigned int __u32;32位无符号整型变量。

通过如下代码(kdev.h中)

#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)
#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

发现高12位代表主设备号,低20位代表次设备号,通过命令行输入cat /proc/devices 发现如下输出:第一个为设备号,第二个为设备名,得到200的主设备号没有使用。可以定义本次主设备号为200。设备名可以为chardev。

Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  6 lp
  7 vcs
 10 misc
 13 input
 21 sg
 29 fb
 81 video4linux
 89 i2c
 99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
202 cpu/msr
204 ttyMAX
216 rfcomm
226 drm
234 kfd
235 aux
236 media
237 cec
238 lirc
239 nvme-generic
240 nvme
241 hidraw
242 ttyDBC
243 bsg
244 watchdog
245 remoteproc
246 ptp
247 pps
248 rtc
249 dma_heap
250 dax
251 dimmctl
252 ndctl
253 tpm
254 gpiochip
261 accel
Block devices:
  7 loop
  8 sd
  9 md
 11 sr
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
252 device-mapper
253 virtblk
254 mdp
259 blkext

第三个参数是一个结构体,通过上图可知该结构体有open,write等函数,此时仅需定义一个该类型的结构体即可。可以参考如下搭一个简单的驱动

#include 
#include 
#define CHARDEVBASE 200            //定义主设备号 200
#define dev_name "chardev"           //定义设备名
//写open
/*
 * @descripts     : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *
 * @return        : 0 成功;其他 失败
 */
static int chardev_open(struct inode *inode, struct file *file){
    printk("chardevbase_open\n");
    return 0;
}
//写release
/*
 * @descripts     : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *
 * @return        : 0 成功;其他 失败
 */
static int chardev_release(struct inode *inode, struct file *file){
    printk("chardevbase_release\n");
    return 0;
}
static ssize_t chardev_read(struct file *, char __user *, size_t, loff_t *){
    printk("chardevbase_read\n");
    return 0;
}
static ssize_t chardev_write(struct file *, const char __user *, size_t, loff_t *){
    printk("chardevbase_write\n");
    return 0;
}
static struct file_operations chardev_file = {
    .owner = THIS_MODULE,
    .open = chardev_open,
    .release = chardev_release,
    .read = chardev_read,
    .write = chardev_write,
};
static int __init chardevbase_init(void)
{
    int ret = 0; //接收注册设备的返回值
    /*注册字符设备*/
    ret = register_chrdev(CHARDEVBASE, dev_name,&chardev_file);
    if (ret < 0)
    {
        printk(KERN_WARNING "coudle not get major number,chrdevbase init fail\n");
    }
    printk("driver init!!!!\n");
    return 0;
}
static void __exit chardevbase_exit(void)
{
    printk("chardevbase: module unloaded\n");
    /*注销字符设备*/
    unregister_chrdev(CHARDEVBASE, dev_name);
}
/*
    模块入口与出口
*/
module_init(chardevbase_init); /* 入口 */
module_exit(chardevbase_exit);/* 出口 */
MODULE_LICENSE("GPL");

1.2.2 我的第一个开发板驱动(Jeston nano )

通过如下命令uname -a查找自己开发板的架构

Linux wheeltec 5.10.216-tegra #1 SMP PREEMPT Tue Mar 4 01:35:16 PST 2025 aarch64 aarch64 aarch64 GNU/Linux

第二步通过uname -r查看内核版本

5.10.216-tegra

第三步查看自己的内核所在地

cd /
sudo find -name "fs.h"

输出如下:

wheeltec@wheeltec:/$ sudo find -name "fs.h"
./var/lib/docker/overlay2/7ab8244ee411089a0ed93ed354a24f284c691be259e4e1254c675c0af07fffc2/diff/usr/include/linux/fs.h
./var/lib/docker/overlay2/7ab8244ee411089a0ed93ed354a24f284c691be259e4e1254c675c0af07fffc2/diff/usr/lib/aarch64-linux-gnu/openmpi/include/openmpi/ompi/mca/fs/fs.h
./var/lib/docker/overlay2/aa3e65839ccf6918d5e2f06b3addaae9fa5603bda8e402e12d255ffe25341d09/diff/usr/include/linux/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/include/linux/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/lib/aarch64-linux-gnu/openmpi/include/openmpi/ompi/mca/fs/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/uapi/linux/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/btrfs/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/debug/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/usb/f/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/usb/configfs/f/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/msdos/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/exfat/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/ext4/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/fuse/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/nfs/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/proc/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/snd/proc/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/blk/debug/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/overlay/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/set/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/autofs/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/ext3/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/configfs/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/fat/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/scsi/proc/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/vfat/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/efivar/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/iso9660/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/9p/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/ntfs/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/autofs4/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/linux/fs.h
./timeshift/snapshots/2025-10-28_08-53-15/localhost/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/linux/mlx5/fs.h
./usr/include/linux/fs.h
./usr/lib/aarch64-linux-gnu/openmpi/include/openmpi/ompi/mca/fs/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/uapi/linux/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/btrfs/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/debug/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/usb/f/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/usb/configfs/f/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/msdos/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/exfat/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/ext4/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/fuse/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/nfs/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/proc/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/snd/proc/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/blk/debug/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/overlay/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/set/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/autofs/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/ext3/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/configfs/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/fat/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/scsi/proc/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/vfat/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/efivar/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/iso9660/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/9p/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/ntfs/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/config/autofs4/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/linux/fs.h
./usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10/include/linux/mlx5/fs.h
find: ‘./run/user/1000/gvfs’: Permission denied
wheeltec@wheeltec:/$

内核版本我查了一下发现内核的源码在/usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10。(一般是在/usr/src)。然后可以去fs.h文件下找到file_operations结构体发现和上面的截图有点不太一样。

然后你可以查找read函数发现如下,索引到simple_attr_read

static const struct file_operations __fops = {                \
    .owner     = THIS_MODULE,                        \
    .open     = __fops ## _open,                    \
    .release = simple_attr_release,                    \
    .read     = simple_attr_read,                    \
    .write     = (__is_signed) ? simple_attr_write_signed : simple_attr_write,    \
    .llseek     = generic_file_llseek,                    \
}
ssize_t simple_attr_read(struct file *file, char __user *buf, size_t len, loff_t *ppos);

发现自己上面写的还是有点问题,修改如下,主要修改了read和write两个函数

#include 
#include 
#define CHARDEVBASE 200            //定义主设备号 200
#define dev_name "chardev"           //定义设备名
//写open
/*
 * @descripts     : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *
 * @return        : 0 成功;其他 失败
 */
static int chardev_open(struct inode *inode, struct file *file){
    printk("chardevbase_open\n");
    return 0;
}
//写release
/*
 * @descripts     : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *
 * @return        : 0 成功;其他 失败
 */
static int chardev_release(struct inode *inode, struct file *file){
    printk("chardevbase_release\n");
    return 0;
}
static ssize_t chardev_read(struct file *file, char __user *buf,size_t len, loff_t *ppos){
    printk("chardevbase_read\n");
    return 0;
}
static ssize_t chardev_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos){
    printk("chardevbase_write\n");
    return 0;
}
static struct file_operations chardev_file = {
    .owner = THIS_MODULE,
    .open = chardev_open,
    .release = chardev_release,
    .read = chardev_read,
    .write = chardev_write,
};
static int __init chardevbase_init(void)
{
    int ret = 0; //接收注册设备的返回值
    /*注册字符设备*/
    ret = register_chrdev(CHARDEVBASE, dev_name,&chardev_file);
    if (ret < 0)
    {
        printk(KERN_WARNING "coudle not get major number,chrdevbase init fail\n");
    }
    printk("driver init!!!!\n");
    return 0;
}
static void __exit chardevbase_exit(void)
{
    printk("chardevbase: module unloaded\n");
    /*注销字符设备*/
    unregister_chrdev(CHARDEVBASE, dev_name);
}
/*
    模块入口与出口
*/
module_init(chardevbase_init); /* 入口 */
module_exit(chardevbase_exit);/* 出口 */
MODULE_LICENSE("GPL");

写一下Makefile,注意KERNELDIR需要改变一下改成上面的。

KERNELDIR := /usr/src/linux-headers-5.10.216-tegra-ubuntu20.04_aarch64/kernel-5.10
CURRENT_PATH := $(shell pwd)
obj-m := chardevbase.o
build: kernel_modules
kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

1.2.3 我的第一个Linux应用

可以通过 man 123 read , man 123 write 和 open 以及 close

#include 
#include 
#include 
#include 
#include 
/*
 *  argc : 应用参数个数
 *  argv : 每个参数的内容,以字符串表示
 *  return : 0 成功执行,其他未成功执行
 *  ./chardevbaseapp.c 
 **/
int main(int argc, char *argv[])
{
    int fd = 0;
    char *filename;
    filename = argv[1];
    /*   The return value of open() is a file descriptor 返回值为文件描述符
     *   The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR.  These request opening the file read-only, write-only, or read/write, respectively.
     *   flag参数需要选择一个可用的模式O_RDONLY, O_WRONLY, or O_RDWR
     **/
    char buffer[100];
    char writer_buf[20];
    int ret;
    /*打开文件*/
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("filename:%s file open fail !!!",filename);
        return -1;
    }
    /*读取数据*/
    ret = read(fd,buffer,100);
    if (ret < 0)
    {
        printf("filename:%s file read fail !!!",filename);
        return -1;
    }
    /*写数据*/
    ret = write(fd,writer_buf,20);
    if (ret < 0)
    {
        printf("filename:%s file wirte fail !!!",filename);
        return -1;
    }
    /*关闭文件*/
    ret = close(fd);
    if (ret < 0)
    {
        printf("filename:%s file close fail !!!",filename);
        return -1;
    }
    return 0;
}

1.2.4 驱动的载入

cd "Makefile文件夹下"

make
sudo insmod chardevbase.ko

实现驱动加载的调试,打印相关输出!!!(注意输出难以通过打印台打印出来需要通过sudo dmesg | tail -8查看)

输入输出如下

wheeltec@wheeltec:~/driver/1_chrdevbase$ sudo insmod chardevbase.ko
wheeltec@wheeltec:~/driver/1_chrdevbase$ sudo dmesg | tail -1
[ 6176.679700] driver init!!!!

注册设备

解释一下 /dev/(你的设备名#define dev_name "chardev" //定义设备名

c 字符设备 200 主设备号 0 次设备号

 sudo mknod /dev/chardev c 200 0

输出如下:

wheeltec@wheeltec:~/driver/1_chrdevbase$ ll /dev/chardev
crw-r--r-- 1 root root 200, 0 12月  3 01:02 /dev/chardev

然后开发板端通过编译chardevbaseapp

 gcc chardevbaseapp.c -o chardevbaseapp

然后sudo ./chardevbaseapp /dev/chardev

再通过sudo dmesg | tail -5查看输出。

wheeltec@wheeltec:~/driver/1_chrdevbase$ sudo dmesg | tail -5
[ 6176.679700] driver init!!!!
[ 6384.888651] chardevbase_open
[ 6384.888660] chardevbase_read
[ 6384.888661] chardevbase_write
[ 6384.888663] chardevbase_release

1.2.5 驱动的完善

printk有如下

#define KERN_EMERG    KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT    KERN_SOH "1"    /* action must be taken immediately */
#define KERN_CRIT    KERN_SOH "2"    /* critical conditions */
#define KERN_ERR    KERN_SOH "3"    /* error conditions */
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
#define KERN_NOTICE    KERN_SOH "5"    /* normal but significant condition */
#define KERN_INFO    KERN_SOH "6"    /* informational */
#define KERN_DEBUG    KERN_SOH "7"    /* debug-level messages */
#define KERN_DEFAULT    ""        /* the default kernel loglevel */

本部分主要完善对字符设备的写入和读取,实现覆写操作以及读取,由于不能直接操作用户态数据只能操作内核数据(不建议尝试,我尝试了直接死机了)所以只能通过两个函数进行中转。

    /* copy_to_user(void __user *to, const void *from, unsigned long n)
     * to 表示复制的目的地
     * from 表示复制的源地址
     * n 表示复制的字节数。成功则返回0,失败则返回负数
     * 用户从buf读取数据,内核向buf写入数据 ,注意读取多长取决于用户传入的len参数
     */
    ret = copy_to_user(buf,read_buffer,len);
    /* copy_from_user(void *to, const void __user *from, unsigned long n)
     * to 表示复制的目的地
     * from 表示复制的源地址
     * n 表示复制的字节数。成功则返回0,失败则返回负数
     * 用户向buf写入数据,内核从buf读取数据 ,注意写入多长取决于用户传入的len参数
     */
    ret = copy_from_user(write_buffer,buf,len);

好的,接下来可以修改之前的数据

#include 
#include 
#include 
#include 
#define CHARDEVBASE 200            //定义主设备号 200
#define dev_name "chardev"           //定义设备名
#define buffer_size 100                 //定义缓存空间
static char read_buffer[buffer_size];  //读缓存
static char write_buffer[buffer_size]; //写缓存
static char kerneldate[buffer_size] = {"kernel data!"};  //缓存
//写open
/*
 * @descripts     : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *
 * @return        : 0 成功;其他 失败
 */
static int chardev_open(struct inode *inode, struct file *file){
    printk("chardevbase_open\n");
    return 0;
}
//写release
/*
 * @descripts     : 打开设备
 * @param - inode : 传递给驱动的inode
 * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 *
 * @return        : 0 成功;其他 失败
 */
static int chardev_release(struct inode *inode, struct file *file){
    printk("chardevbase_release\n");
    return 0;
}
static ssize_t chardev_read(struct file *file, char __user *buf,size_t len, loff_t *ppos){
    int ret=0;
    //注意不能直接使用buffer,需要使用copy_to_user函数
    /* memcpy(void *p, const void *q, __kernel_size_t size)
     * p 表示复制的目的地
     * q 表示复制的源地址
     * size 表示复制的字节数。成功则返回0,失败则返回负数
     * 将kerneldate的数据复制到read_buffer中
     */
    memcpy(read_buffer,kerneldate,sizeof(kerneldate));
    /* copy_to_user(void __user *to, const void *from, unsigned long n)
     * to 表示复制的目的地
     * from 表示复制的源地址
     * n 表示复制的字节数。成功则返回0,失败则返回负数
     * 用户从buf读取数据,内核向buf写入数据 ,注意读取多长取决于用户传入的len参数
     */
    ret = copy_to_user(buf,read_buffer,len);
    if (ret<0)
    {
        printk(KERN_ERR "chardevbase_read fail\n");
        return -1;
    }else
    {
        printk(KERN_INFO "chardevbase_read success,data is %s,len is %ld\n", read_buffer,len);
    }
    return 0;
}
static ssize_t chardev_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos){
    // printk("chardevbase_write\n");
    int ret=0;
    /* copy_from_user(void *to, const void __user *from, unsigned long n)
     * to 表示复制的目的地
     * from 表示复制的源地址
     * n 表示复制的字节数。成功则返回0,失败则返回负数
     * 用户向buf写入数据,内核从buf读取数据 ,注意写入多长取决于用户传入的len参数
     */
    ret = copy_from_user(write_buffer,buf,len);
    if (ret<0)
    {
        printk(KERN_ERR "chardevbase_write fail\n");
        return -1;
    }
    memcpy(kerneldate,write_buffer,sizeof(write_buffer));
    return 0;
}
static struct file_operations chardev_file = {
    .owner = THIS_MODULE,
    .open = chardev_open,
    .release = chardev_release,
    .read = chardev_read,
    .write = chardev_write,
};
static int __init chardevbase_init(void)
{
    int ret = 0; //接收注册设备的返回值
    /*注册字符设备*/
    ret = register_chrdev(CHARDEVBASE, dev_name,&chardev_file);
    if (ret < 0)
    {
        printk(KERN_WARNING "coudle not get major number,chrdevbase init fail\n");
    }
    printk("driver init!!!!\n");
    return 0;
}
static void __exit chardevbase_exit(void)
{
    printk("chardevbase: module unloaded\n");
    /*注销字符设备*/
    unregister_chrdev(CHARDEVBASE, dev_name);
}
/*
    模块入口与出口
*/
module_init(chardevbase_init); /* 入口 */
module_exit(chardevbase_exit);/* 出口 */
MODULE_LICENSE("GPL");

用户函数也需要修改,尝试多参数的调用,比如可以使用-h获取该如何使用用户函数。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
/*
 *  argc : 应用参数个数
 *  argv : 每个参数的内容,以字符串表示
 *  return : 0 成功执行,其他未成功执行
 *  ./chardevbaseapp 
 **/
int main(int argc, char *argv[])
{
    int fd = 0;
    char *filename;
    filename = argv[1];
    /*   The return value of open() is a file descriptor 返回值为文件描述符
     *   The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR.  These request opening the file read-only, write-only, or read/write, respectively.
     *   flag参数需要选择一个可用的模式O_RDONLY, O_WRONLY, or O_RDWR
     **/
    char buffer[100];
    char writer_buf[100];
    char option_str[100];
    int ret,option=0;
    if (argc<3)
    {
        if (argc==2&&(strcmp(argv[1],"-h")==0))
        {
            printf("Usage:./chardevbaseapp /dev/chardev (choose) (write_data(option))\n");
            printf("choose:1    write data to device\n");
            printf("if you choose write you should input write_data\n");
            printf("choose:2    read data from device\n");
            return 0;
        }
        else
        {
            printf("Try './chardevbaseapp -h' for more information.,param numbers is :%d\n", argc);
            return -1;
        }
    }
    /*打开文件*/
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("filename:%s file open fail !!!",filename);
        return -1;
    }
    option = atoi(argv[2]);
    if (option==1)
    {
         /*读取数据*/
        ret = read(fd,buffer,sizeof(buffer));
        if (ret < 0)
        {
            printf("filename:%s file read fail !!!",filename);
            return -1;
        }else
        {
            printf("filename:%s file read success !!!\n",filename);
            printf("Read data from device: %s\n", buffer);
        }
    }
    else if (option==2)
    {
        if (argc!=4)
        {
            printf("Please input data to write to device!!!\n");
        }
        else
        {
            /*写数据*/
            ret = write(fd,argv[3],strlen(argv[3]));
            if (ret < 0)
            {
                printf("filename:%s file wirte fail !!!",filename);
                return -1;
            }else
            {
                printf("filename:%s file write success !!!\n",filename);
                printf("Write data to device: %s\n", argv[3]);
            }
        }
    }
    else
    {
        printf("option error!!!\n");
    }
    /*关闭文件*/
    ret = close(fd);
    if (ret < 0)
    {
        printf("filename:%s file close fail !!!",filename);
        return -1;
    }
    return 0;
}

当然还是可以通过sudo dmesg查看内核的输出。

posted @ 2026-01-06 12:26  clnchanpin  阅读(13)  评论(0)    收藏  举报