字符设备驱动开发 Linux 设备号 字符设备驱动开发步骤 open 函数调用流程 设备号的组成 设备号的分配 Linux 应用程序对驱动程序的调用 字符设备注册与注销 实现设备的具体操作函数

字符设备驱动简介

          字符设备是 Linux 最基本的设备驱动

          字符设备就是一个一个字节,按字节流进行读写操作,读写数据分 先后顺序

          字符设备驱动包括 点灯、按键、 IIC、 SPI,LCD 等

    Linux 应用程序对驱动程序的调用

             Linux 中一切皆为文件

             驱动加载成功后,会在“/dev”目录下生成一个相应的文件

             此文件就是驱动文件

            open 打开 该驱动函数

            close 关闭 该驱动函数

            write 函数 向此驱动 写入数据

            read 函数从驱动中读取相应的状态

             应用程序 运行在 用户空间

             驱动 运行在 内核空间

      open 函数调用流程

 

// linux-5.5.4/include/linux/fs.h
/* Linux 内核驱动操作函数集合 */

struct file_operations
{ 
    struct module *owner;                              // 该结构体的模块的指针
    loff_t (* llseek) (struct file *, loft_t, int);    // 修改文件当前的读写位置
    /* 读取设备文件 */
    ssize_t (* read) (struct file *, char __user *, size_t, loff_t *);
    /* 向设备文件写入数据 */
    ssize_t (* weite) (struct file *, const char __user *, size_t, loff_t *); 
    ssize_t (* read_iter) (struct kiocb *, struct iov_iter *);                   
    ssize_t (* write_iter) (struct kiocb *, stuct iov_iter *);
    int (* iterate) (struct file *, struct dir_context *);
    /* 查询设备是否可以非阻塞的读写 */
    unsigned int (* poll) (struct file *, struct poll_table_struct *);

    /* 提供对设备的控制功能,32位系统,32位的应用程序调用 */
    long (* unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    /* 提供对设备的控制功能,64位系统,32位的应用程序调用 */
    long (* compat_ioctl) (struct file *, unsigned int, unsigned long); 
    /* 将设备的内存映射到进程空间(用户空间), 不用在用户空间和内核空间之间来回复制 */
    int (* mmap) (struct file *, struct vm_area_struct *);  

    int (* mremap) (struct file *, struct vm_area_struct *);
    int (* open) (struct inode *, struct file *);    // 打开设备文件
    int (* flush) (struct file *, fl_owner_t id);    
    int (* release)(struct inode *, struct file *);    // 释放(关闭)设备文件
    int (* fasync)(int, struct file*, int);    //刷新待处理的数据, 将缓冲区的数据刷新到磁盘
    // ...
}__randomize_layout;

 

字符设备驱动开发步骤

       驱动模块的加载与卸载

          Linux 驱动运行方式:

                驱动编译进 Linux 内核,当 Linux 内核启动时,会运行驱动程序

                驱动编译成模块( .ko ),Linux 内核启动后。

                “ insmod ” 命令 加载驱动模块
                “ rmmod ” 命令卸载具体驱动

         调试驱动时一般将编译为模块,驱动编译为模块最大的好处就是方便开发

// linux-5.5.4/include/linux/module.h

#ifndef MODULE
/**
 * module_init() - driver initialization entry point
 *                  驱动程序初始化函数
 * @x: function to be run at kernel boot time or module insertion
 *      在内核启动或模块插入时运行的函数
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  
 * There can only be one per module.
 * 每个模块只能有一个
 */
#define module_init(x)	__initcall(x);

/**
 * module_exit() - driver exit entry point
 *                 驱动程序退出函数
 * @x: function to be run when driver is removed
 *     删除驱动程序时要运行的函数
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  
 * If the driver is statically compiled into the kernel, 
 * module_exit() has no effect.
 * 如果驱动程序被静态编译到内核中,module_exit()无效
 * There can only be one per module.
 */
#define module_exit(x)	__exitcall(x);

#else /* MODULE */

/*
 * In most cases loadable modules do not need custom
 * initcall levels. There are still some valid cases where
 * a driver may be needed early if built in, and does not
 * matter when built as a loadable module. Like bus
 * snooping debug drivers.
 */

/* Each module must use one module_init(). */
/* 每个模块必须使用一个module_init() */
#define module_init(initfn)					\
	static inline initcall_t __maybe_unused __inittest(void)		\
	{ return initfn; }					\
	int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));

/* This is only required if you want to be unloadable. */
/* 仅当您要卸载时才需要 */
#define module_exit(exitfn)					\
	static inline exitcall_t __maybe_unused __exittest(void)		\
	{ return exitfn; }					\
	void cleanup_module(void) __copy(exitfn) __attribute__((alias(#exitfn)));

#endif

字符设备驱动模块加载和卸载模板
 

// linux-5.5.4/arch/arm/common/locomo.c

/* 驱动入口函数 */
static int __init locomo_init(void)
{
    /* 入口函数具体内容 */
	int ret = bus_register(&locomo_bus_type);
	if (ret == 0)
		platform_driver_register(&locomo_device_driver);
	return ret;
}
/* 驱动出口函数 */
static void __exit locomo_exit(void)
{
    /* 出口函数具体内容 */
	platform_driver_unregister(&locomo_device_driver);
	bus_unregister(&locomo_bus_type);
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(locomo_init);
module_exit(locomo_exit);

      驱动编译完成后,扩展名为.ko

           insmod 用于加载指定的 .ko 模块,不能解决模块的依赖关系
           modprobe 会 分析模块的依赖关系,将所有的依赖模块加载到内核
             默认会去 /lib/modules/<kernel-version> 目录中查找模块, Linux kernel 为 版本号

         modprobe -r 可卸载掉驱动模块所依赖的其他模块 

   字符设备注册与注销

            驱动模块加载成功后,需要注册字符设备

            卸载驱动模块时,需要注销掉字符设备

//linux-5.5.4/include/linux/fs.h

/* 注册字符设备
 * major : 主设备号
 * name :设备名字
 * fops : 指向设备的操作函数集合变量
 */
static inline int register_chrdev(unsigned int major,     
                                  const char *name,
                                  const struct file_operations *fops)
{
    return __register_chardev(major, 0, 256, name, fops);
}

/* 注销字符设备
 * major : 主设备号
 * name :设备名字
 */
static inline void unregister_chrdev(unsigned int major, 
                                     const char *name)
{
	__unregister_chrdev(major, 0, 256, name);
}

// linux-5.5.4/fs/char_dev.c
/**
 * __register_chrdev() - create and register a cdev occupying a range of minors
 * @major: major device number or 0 for dynamic allocation
 * @baseminor: first of the requested range of minor numbers
 * @count: the number of minor numbers required
 * @name: name of this range of devices
 * @fops: file operations associated with this devices
 *
 * If @major == 0 this functions will dynamically allocate a major and return
 * its number.
 *
 * If @major > 0 this function will attempt to reserve a device with the given
 * major number and will return zero on success.
 *
 * Returns a -ve errno on failure.
 *
 * The name of this device has nothing to do with the name of the device in
 * /dev. It only helps to keep track of the different owners of devices. If
 * your module name has only one type of devices it's ok to use e.g. the name
 * of the module here.
 */
int __register_chrdev(unsigned int major, unsigned int baseminor,
		      unsigned int count, const char *name,
		      const struct file_operations *fops)
{
	struct char_device_struct *cd;
	struct cdev *cdev;
	int err = -ENOMEM;

	cd = __register_chrdev_region(major, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);

	cdev = cdev_alloc();
	if (!cdev)
		goto out2;

	cdev->owner = fops->owner;
	cdev->ops = fops;
	kobject_set_name(&cdev->kobj, "%s", name);

	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
	if (err)
		goto out;

	cd->cdev = cdev;

	return major ? 0 : cd->major;
out:
	kobject_put(&cdev->kobj);
out2:
	kfree(__unregister_chrdev_region(cd->major, baseminor, count));
	return err;
}

/**
 * __unregister_chrdev - unregister and destroy a cdev
 *                       注销并销毁 cdev
 * @major: major device number
 *         主设备号
 * @baseminor: first of the range of minor numbers            
 * @count: the number of minor numbers this cdev is occupying
 * @name: name of this range of devices
 *
 * Unregister and destroy the cdev occupying the region described by
 * @major, @baseminor and @count.  This function undoes what
 * __register_chrdev() did.
 */
void __unregister_chrdev(unsigned int major, unsigned int baseminor,
			 unsigned int count, const char *name)
{
	struct char_device_struct *cd;

	cd = __unregister_chrdev_region(major, baseminor, count);
	if (cd && cd->cdev)
		cdev_del(cd->cdev);
	kfree(cd);
}

       输入命令“ cat /proc/devices ”可查看 当前已经被使用掉的设备号
 

// linux-5.5.4/arch/mips/sibyte/common/sb_tbprof.c

/* 主设备号为 240 */
#define SBPROF_TB_MAJOR 240
/* 设备名字为“sb_tbprof” */
#define DEVNAME "sb_tbprof"

/* 设备的操作函数集合 */
static const struct file_operations sbprof_tb_fops = 
{
	.owner		= THIS_MODULE,
	.open		= sbprof_tb_open,
	.release	= sbprof_tb_release,
	.read		= sbprof_tb_read,
	.unlocked_ioctl = sbprof_tb_ioctl,
	.compat_ioctl	= sbprof_tb_ioctl,
	.mmap		= NULL,
	.llseek		= default_llseek,
};

/* 驱动入口函数 */
static int __init sbprof_tb_init(void)
{
	struct device *dev;
	struct class *tbc;
	int err;

    /* 主设备号为 SBPROF_TB_MAJOR
     * 设备名字为“DEVNAME”
     */
    if (register_chrdev(SBPROF_TB_MAJOR, DEVNAME, &sbprof_tb_fops)) 
    {
        /* 字符设备注册失败,自行处理 */
	    printk(KERN_WARNING DEVNAME ": initialization failed (dev %d)\n",
		    SBPROF_TB_MAJOR);
	    return -EIO;
    }

    tbc = class_create(THIS_MODULE, "sb_tracebuffer");
    if (IS_ERR(tbc)) 
    {
	    err = PTR_ERR(tbc);
	    goto out_chrdev;
    }
    // ...
out_chrdev:
	unregister_chrdev(SBPROF_TB_MAJOR, DEVNAME);

	return err;
}

static void __exit sbprof_tb_cleanup(void)
{
    device_destroy(tb_class, MKDEV(SBPROF_TB_MAJOR, 0));
    /* 注销字符设备驱动 */
    unregister_chrdev(SBPROF_TB_MAJOR, DEVNAME);
    class_destroy(tb_class);
}
 /* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(sbprof_tb_init);
module_exit(sbprof_tb_cleanup);

   设备的具体操作函数

            设备 进行读写操作框架

/* 打开设备 */
static int xxx_open(struct inode *inode, struct file *filp)
{
    return 0;
}
/* 从设备读取 */
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}
/* 向设备写数据 */
static ssize_t xxx_write(struct file *file, const char __user *buf, size_t cnt, loff_t *offt)
{
    /* 用户实现具体功能 */
    return 0;
}
 /* 关闭/释放设备 */
static int xxx_release(struct inode *inode, struct file *filp)
{
    /* 用户实现具体功能 */
    return 0;
}

static struct file_oprations xxx_fops = 
{
    .owner = THIS_MODULE,
    .open = xxx_open,
    .read = xxx_read,
    .write = xxx_write,
    .release = xxx.release,
};
/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /* 入口函数具体内容 */
    int retvalue = 0;
    /* 注册字符设备驱动 */
    retvalue = register_chrdev(99, "xxx", &xxx_fops);
    if(retvalue > 0)
    {
        /* 字符设备注册失败,自行处理 */
    }
    return 0;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    /* 注销字符设备驱动 */
    unregister_chrdev(99,"xxx");
}
 /* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

/* LICENSE 采用 GPL 协议 */
MODULE_LICENSE("GPL");
/* 作者名字 */
MODULE_AUTHOR("CpuCode");

 

Linux 设备号

       Linux 中每个设备都有一个设备号
       设备号由主设备号和次设备号

        主设备号:一个驱动,高 12 位为主设备号(0 ~ 4095)

        次设备号:使用该驱动的各个设备,低 20 位为次设备号
 

// linux-5.5.4/inlcude/linux/types.h

typedef u32 __kernel_dev_t;
typedef __kernel_dev_t		dev_t;

// linux-5.5.4/include/uapi/asm-generic/int-ll64.h
typedef __u32 u32;
typedef unsigned int __u32;

// linux-5.5.4/include/linux/kdev_t.h

#define MINORBITS	20        //次设备号位数
#define MINORMASK	((1U << MINORBITS) - 1)    //次设备号掩码

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))    // dev_t 中获取主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))    // dev_t 中获取次设备号

/* 给定的主设备号和次设备号的值组合成 dev_t 类型的设备号 */
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))    

     设备号的分配

            静态分配设备号

cat /proc/devices    // 查看系统使用的所有设备号

           动态分配设备号

// linux-5.5.4/fs/char_dev.c

/**
 * alloc_chrdev_region() - register a range of char device numbers
 *                         注册一系列字符设备号
 * @dev: output parameter for first assigned number
 *       第一个分配编号的输出参数
 * @baseminor: first of the requested range of minor numbers
 *             第一个请求的次设备数字
 * @count: the number of minor numbers required
 *         次设备的数量
 * @name: the name of the associated device or driver
 *        相关设备或驱动程序的名称
 *
 * Allocates a range of char device numbers.  
 *  分配一系列字符设备号
 * The major number will be chosen dynamically, and returned 
 * (along with the first minor number)in @dev.
 * Returns zero or a negative error code.
 *   返回零或负错误代码
 */
int alloc_chrdev_region(det_t *dev, unsigned baseminor, unsigned count, const char *name)
{
    struct char_device_struct *cd;
    cd = __register_chrdev_region(0, baseminor, count, name);

    if(IS_ERR(cd))
    {
        return PTR_ERR(cd);
    }
    *dev = MKDEV(cd -> major, cd -> baseminor);
    return 0;
}

/**
 * unregister_chrdev_region() - unregister a range of device numbers
 * @from: the first in the range of numbers to unregister
 *        注销的数字范围中的第一个
 * @count: the number of device numbers to unregister
 *         注销的设备号的数量
 *
 * This function will unregister a range of @count device numbers,starting with @from.
 * 取消注册一系列@count设备数量,从@from开始
 * The caller should normally be the one who allocated those numbers in the first place..
 * 
 */
void unregister_chrdev_region(dev_t from, unsigned count)
{
    dev_t to = from + count;
    dev_t n, next;

    for (n = from; n < to; n = next) 
    {
	next = MKDEV(MAJOR(n)+1, 0);

	if (next > to)
        {
            next = to;
        }  
	kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    }
}

 添加头文件路径

      按下“ Crtl+Shift+P ”打开 VSCode 的控制台,然后输入“C/C++: Edit configurations(JSON) ”,打开 C/C++编辑配置文件,

      .vscode 目录下生成一个名为 c_cpp_properties.json 的文件

         includePath 表示 头文件路径 ( 绝对路径 )

{
    "configurations": 
    [
        {
            "name": "Linux",
            "includePath": 
            [
                "${workspaceFolder}/**",
                "/home/.../linux-5.5.4/include",
                "/home/.../linux-5.5.4/arch/arm/include",
                "/home/.../linux-5.5.4/arch/arm/include/generated/"
            ],
            "defines": [],
            // ...
        }
    ],
    "version": 4
}

  编写程序:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>

// 主设备号
#define    CHRDEVBASE_MAJOR    99
// 设备名
#define    CHRDEVBASE_NAME    "chrdevbase"

// 读缓冲区
static char readbuf[100];
// 写缓冲区
static char writebuf[100];
static char kerneldata[] = {"kernel data"};

/*
 * 打开设备
 * inode:传递驱动inode
 * filp:设备文件
 * return: 0:成功 其他:失败
 */
static int chrdevbase_open(struct inode*inode, struct file *filp)
{
    // printf:用户态 printk:内核态 1.1
    printk("chrdevbase_open \r\n");
    return 0;
}

/*
 * 读数据
 * filp: 设备文件(文件描述符)
 * buf: 返回给用户空间的数据缓冲区
 * cnt: 数据长度
 * offt:相当于文件首地址的偏移
 * return: 成功:字节数 失败:负值
 */
static ssize_t chrdevbase_read(struct file *filp, 
                               char __user *buf, 
                               ssize_t cnt, 
                               loff_t *offt)
{
    int retvalue = 0;

    printk("chrdevbase read!\r\n");
    //  kerneldata 数组中的数据拷贝到读缓冲区 readbuf 
    memcpy(readbuf, kerneldata, sizeof(kerneldata)):
    /* readbuf 中的数据复制到参数 buf
     * 内核空间不能直接操作用户空间的内存
     */
    retvalue = copy_to_user(buf, readbuf, cnt);

    if(retvalue == 0)
    {
        printk("kernel senddata ok \r\n");
    }
    else
    {
        printk("kernel senddata failed \r\n");

    }

    return 0;
}

/*
 * 写数据
 * filp:设备文件
 * buf:写入数据
 * cnt:数据长度
 * offt:对于文件首地址的偏移量
 * return:成功:字节数 失败:负数
 */
static ssize_t chrdevbase_write(struct file *filp,
                                const char __user *buf, 
                                size_t cnt, 
                                loff_t *offt)
{
    int retvalue = 0;

    printk("chrdevbase write \r\n");
    /* buf 中的数据复制到写缓冲区 writebuf
     * 用户空间内存不能直接访问内核空间的内存
     */
    retvalue = copy_from_user(writebuf, buf, cnt);
    
    if(retvalue == 0)
    {
        printk("kernel recevdata:%s \r\n", writebuf);
    }
    else
    {
        printk("kernel recevdata failed \r\n");
    }

    return 0;
}

/*
 * 关闭设备
 * filp:关闭设备文件
 * return: 成功:0 失败:其他
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    printk("chrdevbase release \r\n");
    return 0;
}

// 设备操作函数结构体
static struct file_operations chrdevbase_fops =
{
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

/*
 * 驱动入口
 * return:成功:0 失败: 其他
 */
static int __init chrdevbase_init(void)
{
    int retvalue =0;
    printk("chrdevbase_init \r\n");

    // 注册字符设备驱动
    retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    
    if(retvalue < 0)
    {
        printk("chrdevbase driver register failed \r\n");
    }

    return 0;
}

// 驱动出口
static void __exit chrdevbase_exit(void)
{
    // 注销字符设备驱动
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    printk("chrdevbase_exit() \r\n");
}

// 驱动入口 出口
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

// LICENSE 作者
MODULE_LICENSE("GPL");
MODULE_AUTHOR("CpuCode");

 

/* linux-5.5.4/linux/kern_levels.h */
/* 1.1
 *  0 的优先级最高 7 的优先级最低
 *
 */
#define KERN_SOH	"\001"		/* ASCII Start Of Header */
#define KERN_SOH_ASCII	'\001'

#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 */

// linux-5.5.4/include/linux/printk.h
/* printk's without a loglevel use this.. */
#define MESSAGE_LOGLEVEL_DEFAULT CONFIG_MESSAGE_LOGLEVEL_DEFAULT
/*
 * Default used to be hard-coded at 7, quiet used to be hardcoded at 4,
 * we're now allowing both to be set from kernel config.
 */
#define CONSOLE_LOGLEVEL_DEFAULT CONFIG_CONSOLE_LOGLEVEL_DEFAULT

//linux-5.5.7/include/generated/autoconf.h
#define CONFIG_MESSAGE_LOGLEVEL_DEFAULT 4
#define CONFIG_CONSOLE_LOGLEVEL_DEFAULT 7
//linux-5.5.7/tools/virtio/linux/uaccess.h

static inline int copy_from_user(void *to, const void __user volatile *from,
				 unsigned long n)
{
	__chk_user_ptr(from, n);
	volatile_memcpy(to, from, n);
	return 0;
}

static inline int copy_to_user(void __user volatile *to, const void *from,
			       unsigned long n)
{
	__chk_user_ptr(to, n);
	volatile_memcpy(to, from, n);
	return 0;
}

   编写应用程序

      C 库文件操作基本函数

                open 函数

/*
 * pathname:打开的设备或 文件名
 * return: 成功:文件描述符
 */
int open(const char *pathname, int flags)

             flags: 文件打开模式

          O_RDONLY    只读模式

          O_WRONLY   只写模式

          O_RDWR        读写模式

           O_APPEND       每次写操作都写入文件的末尾

           O_CREAT           如文件不存在,就创建该文件

           O_EXCL            如文件存在,返回-1 并修改errno的值

           O_TRUNC         如文件存在,并以只写/读写打开,清空文件全部内容

           O_NOCTTY       如路径名指终端设备,不作控制终端

           O_NONBLOCK  

           DSYNC            

           O_RSYNC        

           O_SYNC         

 

            read 函数

/*
 * fd:文件描述符
 * buf:数据读取到buf
 * count:数据长度
 * return:成功:字节数,文件名末尾返回0  失败:负值
 */
ssize_t read(int fd, void *buf, size_t count)

          write 函数

/*
 * fd: 文件描述符
 * buf:写入的数据
 * count:数据长度
 * return:成功:字节数 没有数据返回0 失败:负值
 */
ssize_t write(int fd, const void *buf, size_t count)

           close 函数

/*
 * fd:文件描述符
 * return:成功:0 失败:负值
 */
int close(int fd)

应用程序:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

// 测试数据
static char usrdata[] = {"usr data"};

/*
 * argc: argv数组个数
 * argv:命令行参数
 * return:成功:0 失败:负值
 */
int main(int argc, char *argv[])
{
    int fd;
    int retalue;
    char *filename;
    char readbuf[100],writebuf[100];
    
    // 判断参数是否为 3
    if(argc != 3)
    {
        printf("Error Usage \r\n");
        return -1;
    }

    // 设备名
    filename = argv[1];
    
    // 打开驱动文件
    fd = open(filename, O_RDWR);

    if(fd < 0)
    {
        printf("Can't open file %s \r\n", filename);
        return -1;
    }

    // 读取数据,atoi 字符数转换为数字
    if(atoi(argv[2]) == 1)
    {
        //驱动文件中读取数据
        retvalue = read(fd, readbuf, 50);

        if(retvalue < 0)
        {
            printf("read file %s failed \r\n", filename);
        }
        else
        {
            // 读取成功,打印数据
            printf("read data %s \r\n", readbuf);
        }
    }

    // 写数据
    if(atoi(argv[2]) == 2)
    {
        // 写数据
        memcpy(writebuf, usrdata, sizeof(usrdata));
        retvalue = write(fd, writebuf, 50);
        if(retvalue < 0)
        {
            printf("write file %s failed \r\n", filename);
        }
    }

    // 关闭设备
    retvalue = close(fd);
    
    if(retvalue < 0)
    {
        printf("can't close file %s \r\n", filename);
        return -1;
    }

    return 0;
}

Makefile:

# linux内核的绝对路径
KERNELDIR := /home/cpucode/linux-5.5.7
# 当前路径
CPRRENT_PATH := $(shell pwd)
#将文件编译为ko模块
obj-m := chrdevbase.o

build:kernel_modules

# modules :编译模块
# -C:当前目录切换到KERNELDIR
# M:模块源码目录
kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译驱动程序:

make

编译应用程序:

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

#查看程序信息
file chrdevbaseApp

加载驱动文件

modprobe chrdevbase.ko
# 查看模块
lsmod
# 查看所有设备
cat /proc/devices
# mknod:创建节点
# chrdevbase:创建节点目录
# c:字符设备
# 99: 主设备
# 9:次数
mknod /dev/chrdevbase c 99 9
#卸载模块
rmmod chrdevbase.ko

 

全部评论

相关推荐

10-06 12:46
门头沟学院 Java
跨考小白:定时任务启动
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务