Linux字符设备驱动开发之内存读写的应用实例

字符设备是Linux系统三大类设备之一(字符设备、块设备、网络设备),作为Linux最简单的一类设备,字符设备常用来传输一些简单的控制命令或者少量的数据。本篇文章分享了如何在Linux内核中创建一个字符设备,并在应用程序中测试该设备的实例。该字符设备通过在内核中创建一段内存空间,并将这段空间作为字符设备读写访问的目标地址,来实现Linux内核字符设备驱动与应用程序的通信。

1. 操作系统

Linux Ubuntu 18.04, 内核版本:4.18

2. 驱动程序

在内核中创建一个字符设备驱动程序virtual_disk.c,并用模块化的编译方式将其编译成virtual_disk.ko文件并加载到操作系统中。

因为用内核中一段内存空间作为设备虚拟的磁盘访问空间,我们可以假定有一个虚拟的磁盘可以供设备访问。

驱动程序virtual_disk.c源代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define VIRTUALDISK_SIZE	0x2000
#define MEM_CLEAR			0x1
#define PORT1_SET			0x2
#define PORT2_SET			0x3
#define VIRTUALDISK_MAJOR	200

static int VirtualDisk_major = VIRTUALDISK_MAJOR;
static void *VirtualDisk_devp = NULL;

struct VirtualDisk
{
	struct cdev cdev;
	unsigned char mem[VIRTUALDISK_SIZE];
	int port1;
	long port2;
	long count;
};

static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minor);
static int VirtualDisk_open(struct inode *inode, struct file *filp);
static int VirtualDisk_release(struct inode *inode, struct file *filp);
static ssize_t VirtualDisk_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos);
static ssize_t VirtualDisk_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos);
static loff_t VirtualDisk_llseek(struct file *filp, loff_t offset, int orig);

static const struct file_operations VirtualDisk_fops =
{
	.owner		= THIS_MODULE,
	.llseek		= VirtualDisk_llseek,
	.read		= VirtualDisk_read,
	.write		= VirtualDisk_write,
	.open		= VirtualDisk_open,
	.release    = VirtualDisk_release,
};

static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minor)
{
	int err;
	dev_t devno = MKDEV(VirtualDisk_major, 0);
	cdev_init(&dev->cdev, &VirtualDisk_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &VirtualDisk_fops;
	err = cdev_add(&dev->cdev, devno, 1);
	if (err)
	{
		printk(KERN_NOTICE "Error in cdev_add() \n");
	}
}

static int VirtualDisk_open(struct inode *inode, struct file *filp)
{
	struct VirtualDisk *devp = NULL;
	
	filp->private_data = VirtualDisk_devp;
	devp = filp->private_data;
	devp->count++;
	return 0;
}

static int VirtualDisk_release(struct inode *inode, struct file *filp)
{
	struct VirtualDisk *devp = filp->private_data;
	devp->count--;
	return 0;
}

static ssize_t VirtualDisk_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct VirtualDisk *devp = filp->private_data;

	if (p >= VIRTUALDISK_SIZE)
	{
		return count ? -ENXIO : 0;
	}
	if (count > VIRTUALDISK_SIZE - p)
	{
		count = VIRTUALDISK_SIZE - p;
	}

	if (copy_to_user(buf, (void *)(devp->mem + p), count))
	{
		ret = -EFAULT;
	}
	else
	{
		*ppos += count;
		ret = count;
		printk(KERN_INFO "read %d bytes from %lx\n", count, p);
	}
	return ret;
}

static ssize_t VirtualDisk_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct VirtualDisk *devp = filp->private_data;

	if (p >= VIRTUALDISK_SIZE)
	{
		return count ? -ENXIO : 0;
	}
	if (count > VIRTUALDISK_SIZE - p)
	{
		count = VIRTUALDISK_SIZE - p;
	}

	if (copy_from_user((void *)(devp->mem + p), buf, count))
	{
		ret = -EFAULT;
	}
	else
	{
		*ppos += count;
		ret = count;
		printk(KERN_INFO "written %d bytes to %lx\n", count, p);
	}
	return ret;
}

static loff_t VirtualDisk_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;

	switch (orig)
	{
		case SEEK_SET:
			if (offset < 0)
			{
				ret = -EINVAL;
				break;
			}
			if ((unsigned int)offset > VIRTUALDISK_SIZE)
			{
				ret = -EINVAL;
				break;
			}
			filp->f_pos = offset;
			ret = filp->f_pos;
			break;
		case SEEK_CUR:
			if ((filp->f_pos + offset) > VIRTUALDISK_SIZE)
			{
				ret = -EINVAL;
				break;
			}
			if ((filp->f_pos + offset) < 0)
			{
				ret = -EINVAL;
				break;
			}
			filp->f_pos += offset;
			ret = filp->f_pos;
			break;
		default:
			ret = -EINVAL;
			break;
	}
	return ret;
}

/* Device driver module init function */
static int __init VirtualDisk_init(void)
{
	int ret;
	dev_t devno = MKDEV(VirtualDisk_major, 0);
	if (VirtualDisk_major)
	{
		ret = register_chrdev_region(devno, 1, "VirtualDisk");
	}
	else
	{
		ret = alloc_chrdev_region(&devno, 0, 1, "VirtualDisk");
		VirtualDisk_major = MAJOR(devno);
	}
	if (ret < 0)
	{
		return ret;
	}

	VirtualDisk_devp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL);
	if (!VirtualDisk_devp)
	{
		ret = -ENOMEM;
		goto fail_kmalloc;
	}
	memset(VirtualDisk_devp, 0, sizeof(struct VirtualDisk));

	VirtualDisk_setup_cdev(VirtualDisk_devp, 0);
	return 0;
	fail_kmalloc:
		unregister_chrdev_region(devno, 1);
		return ret;
}

/* Driver module exit function */
static void __exit VirtualDisk_exit(void)
{
	struct VirtualDisk *devp = NULL;

	devp = VirtualDisk_devp;
	cdev_del(&devp->cdev);
	kfree(VirtualDisk_devp);
	unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
}

module_init(VirtualDisk_init);
module_exit(VirtualDisk_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("VirtualDisk character device driver");
MODULE_AUTHOR("Jack");
MODULE_VERSION("V1.0");

上面的驱动代码实现了在内核中创建一个名称为“VirtualDisk”的字符设备驱动,该字符设备的主设备号为200,次设备号为0。设备可访问的内存空间大小为8K。

3. Makefile文件

有了驱动程序,还需要创建一个Makefile文件才能进行编译。Makefile文件内容如下:

ifneq ($(KERNELRELEASE),)
obj-m := virtual_disk.o

else
CC := gcc
KERNEL_DIR = /usr/src/linux-headers-$(uname -r)
PWD := $(shell pwd)

modules:
        $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
modules_install:
        $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
clean:
        rm -f *.o
        rm -f *.o.*
        rm -f *.symvers
        rm -f *.order
        rm -f *.ko
        rm -f *.ko.*
        rm -f *.mod.c
        rm -f *.mod.*
        rm -f .counter.*
        rm -rf .tmp_versions

创建好Makefile后,给它增加执行权限"chmod a+x Makefile"。然后执行“make”进行编译,编译后会生成virtual_disk.ko的模块文件。

4. 加载驱动模块文件

在命令行执行“insmod virtual_disk.ko”加载模块文件,并执行"lsmod”查看模块文件。

[root@stage4 kernel]# insmod virtual_disk.ko
insmod virtual_disk.ko
[  100.418030] virtual_disk: loading out-of-tree module taints kernel.
[  100.419660] virtual_disk: module verification failed: signature and/or required key missing - tainting kernel
[root@stage4 app]# lsmod
lsmod
Module                  Size  Used by
virtual_disk           11884  0
[root@stage4 app]# 

执行命令“cat /proc/devices”可以查看到设备名称和主设备号。

[root@stage4 app]# cat /proc/devices
cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 21 sg
128 ptm
136 pts
162 raw
200 VirtualDisk
229 hvc
245 hidraw

该设备需要通过手动添加设备文件,使用mknod命令添加设备文件,如下。

[root@stage4 kernel]# mknod /dev/virtual_disk c 200 0
mknod /dev/virtual_disk c 200 0

再查看/dev/目录,就可以看到生成了名字为“virtual_disk”的字符设备文件。

[root@stage4 app]# ls -l /dev
ls -l /dev
total 0
crw-r--r-- 1 root root     10, 235 Jan 11 12:15 autofs
drwxr-xr-x 2 root root          60 Jan 11 12:15 block
drwxr-xr-x 2 root root        2940 Jan 11 12:15 char
...
crw-r--r-- 1 root root    200,   0 Jan 11 12:16 virtual_disk
crw-rw-rw- 1 root root      1,   5 Jan 11 12:15 zero

5. 测试程序

编写测试程序virtual_disk_test.c,代码如下。

#include 
#include 
#include 
#include 
#include 
#include 

#define MAX_LEN (100)

int main()
{
    int fd;
	int str_len = 0;
    char write_buf[255] = { 0 };
    char read_buf[255]  = { 0 };
    
    printf("please input a string:\n");
    scanf("%s", write_buf);
    str_len = strlen(write_buf);
	if (str_len > MAX_LEN)
	{
		printf("Error, the input string length is beyond the maximum str length.\n");
		return -1;
	}

    fd = open("/dev/virtual_disk", O_RDWR);
    if(fd < 0)
	{
        printf("virtual_disk device open fail\n");
        return -1;
    }
	lseek(fd, 0, SEEK_SET);
	
    printf("Write %d bytes data to /dev/virtual_disk \n", str_len);
    printf("%s\n", write_buf);

    write(fd, write_buf, str_len);

	lseek(fd, 0, SEEK_SET);
    printf("Read %d bytes data from /dev/virtual_disk \n", str_len);
    read(fd, read_buf, str_len);
    printf("%s\n", read_buf);
    
    close(fd);
    return 0;

}

将virtual_disk_test.c源程序编译生成可执行程序virtual_disk.elf:

gcc -o virtual_disk.elf virtual_disk_test.c

执行virtual_disk.elf程序:

[root@stage4 app]# ./virtual_disk.elf
./virtual_disk.elf
please input a string:
HelloWorld
HelloWorld
Write 10 bytes data to /dev/virtual_disk 
HelloWorld
[ 2002.463060] written 10 bytes to 0
Read 10 bytes data from /dev/virtual_disk 
[ 2002.464765] read 10 bytes from 0
HelloWorld
[root@stage4 app]# 

可以看到通过创建的字符设备,成功地将字符串“HelloWorld”写入到内核中,再次读出到用户空间。

你可能感兴趣的:(Linux,C/C++,嵌入式)