【linux驱动】简单字符设备驱动

1. 设备文件相关结构体和函数

一个 Linux 系统,默认最大的主设备号是 255,结构体如下

// include/linux/fs.h
#define CHRDEV_MAJOR_HASH_SIZE 255

// /fs/char_dev.c
static struct char_device_struct {
	struct char_device_struct *next;
	unsigned int major;
	unsigned int baseminor;
	int minorct;
	char name[64];
	struct cdev *cdev;		/* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

设备注册函数

// include/linux/fs.h
// 向内核注册了一个字符设备
static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}
/**
 * __register_chrdev() - create and register a cdev occupying a range of minors
 * @major: major device number or 0 for dynamic allocation   主设备号或者为 0 就是动态分配
 * @baseminor: first of the requested range of minor numbers 次设备号,register_chrdev 直接使用了 0
 * @count: the number of minor numbers required                    
 * @name: name of this range of devices                      设备名字
 * @fops: file operations associated with this devices       文件结构体(Linux 万物皆文件,靠的就是这个结构体)
 *
 * 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;
}

3. 例子代码

  • chrdevbase.c
#include 
#include 
#include 
#include 
#include 
#include 

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

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

/*
 * @description : 打开设备
 * @param – inode : 传递给驱动的 inode
 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    // printk("chrdevbase open!\r\n");
    return 0;
}

/*
 * @description : 从设备读取数据
 * @param - filp : 要打开的设备文件(文件描述符)
 * @param - buf : 返回给用户空间的数据缓冲区
 * @param - cnt : 要读取的数据长度
 * @param - offt : 相对于文件首地址的偏移
 * @return : 读取的字节数,如果为负值,表示读取失败
 */

static ssize_t chrdevbase_read(struct file *filp, char __user *buf,
                               size_t cnt, loff_t *offt)
{
    int retvalue = 0;

    /* 向用户空间发送数据 */
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    retvalue = copy_to_user(buf, readbuf, cnt);
    if (retvalue == 0)
    {
        printk("kernel senddata ok!\r\n");
    }
    else
    {
        printk("kernel senddata failed!\r\n");
    }

    // printk("chrdevbase read!\r\n");
    return 0;
}

/*
 * @description : 向设备写数据
 * @param - filp : 设备文件,表示打开的文件描述符
 * @param - buf : 要写给设备写入的数据
 * @param - cnt : 要写入的数据长度
 * @param - offt : 相对于文件首地址的偏移
 * @return : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp,
                                const char __user *buf,
                                size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    /* 接收用户空间传递给内核的数据并且打印出来 */
    retvalue = copy_from_user(writebuf, buf, cnt);
    if (retvalue == 0)
    {
        printk("kernel recevdata:%s\r\n", writebuf);
    }
    else
    {
        printk("kernel recevdata failed!\r\n");
    }

    // printk("chrdevbase write!\r\n");
    return 0;
}

/*
 * @description : 关闭/释放设备
 * @param - 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,
};

/*
 * @description : 驱动入口函数
 * @param : 无
 * @return : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    int retvalue = 0;

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

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
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("chenzhiyong");
  • Makefile
obj-m += chrdevbase.o

#generate the path
CURRENT_PATH:=$(shell pwd)

#the current kernel version number
LINUX_KERNEL:=$(shell uname -r)

#the absolute path
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)

#complie object
all:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
#clean
clean:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean


  1. 编译:make ,生成 *.ko 文件,它就是内核模块。
  2. 插入内核模块:insmod chrdevbase.ko
  3. 查看内核模块是否成功:dmesg | tail -n 20
[    9.261691] rfkill: input handler disabled
[  345.463456] kauditd_printk_skb: 32 callbacks suppressed
[  345.463458] audit: type=1400 audit(1697963648.793:43): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="/snap/core/16202/usr/lib/snapd/snap-confine" pid=2103 comm="apparmor_parser"
[  345.463466] audit: type=1400 audit(1697963648.793:44): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="/snap/core/16202/usr/lib/snapd/snap-confine//mount-namespace-capture-helper" pid=2103 comm="apparmor_parser"
[  346.309668] audit: type=1400 audit(1697963649.640:45): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap.gnome-system-monitor.hook.configure" pid=2107 comm="apparmor_parser"
[  346.777464] audit: type=1400 audit(1697963650.108:46): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap.gnome-system-monitor.gnome-system-monitor" pid=2106 comm="apparmor_parser"
[  348.399012] audit: type=1400 audit(1697963651.727:47): apparmor="STATUS" operation="profile_replace" profile="unconfined" name="snap-update-ns.gnome-system-monitor" pid=2105 comm="apparmor_parser"
[  348.403846] audit: type=1400 audit(1697963651.731:48): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="snap-update-ns.core" pid=2109 comm="apparmor_parser"
[  348.405261] audit: type=1400 audit(1697963651.735:49): apparmor="STATUS" operation="profile_replace" info="same as current profile, skipping" profile="unconfined" name="snap.core.hook.configure" pid=2110 comm="apparmor_parser"
[13529.015625] chrdevbase: loading out-of-tree module taints kernel.
[13529.015665] chrdevbase: module verification failed: signature and/or required key missing - tainting kernel
[13529.016265] chrdevbase driver register success
[13529.016266] chrdevbase_init()
  1. 查看设备列表:
    cat /proc/devices
......
180 usb
189 usb_device
200 chrdevbase	# 设备号 200 就是我们代码中的设备号
204 ttyMAX
216 rfcomm
226 drm
238 kfd
  1. 创建设备,跟设备号绑定:mknod /dev/chrdevbase c 200 0

4. 测试

  • 测试文件
  • test.c
#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!"};

/**
 * @brief main 函数
 *
 * @param argc
 * @param argv
 * @return int
 */
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    char readbuf[100], writebuf[100];
    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;
    }
    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;
}
  1. 编译:gcc -o test test.c
  2. 运行:
    从设备文件读: ./test /dev/chrdevbase 1 , 往设备文件写: /test /dev/chrdevbase 2

你可能感兴趣的:(linux,内核,驱动开发)