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

23 篇文章 1 订阅
13 篇文章 0 订阅

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

1. 操作系统

Linux Ubuntu 18.04, 内核版本:4.18

2. 驱动程序

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

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

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

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <asm/io.h>


#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 <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>

#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”写入到内核中,再次读出到用户空间。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Linux字符设备驱动开发是指在Linux系统中编驱动程序,使得用户可以通过字符设备接口来访问硬件设备。这种驱动程序通常用于控制串口、并口、USB设备等。开发Linux字符设备驱动需要掌握Linux内核的基本知识,包括进程管理、内存管理、中断处理、设备驱动等方面。此外,还需要了解字符设备驱动的编流程、驱动程序的结构和接口等。开发Linux字符设备驱动需要使用C语言和Linux内核编程接口。 ### 回答2: Linux字符设备驱动开发Linux系统中的一部分,它允许开发人员在Linux系统上使用字符设备,这些字符设备可以包括串口、USB口、网卡等。Linux字符设备驱动开发可帮助开发人员实现各种各样的设备驱动,从而增强Linux系统的功能。 在Linux字符设备驱动的开发过程中,需要注意以下几点: 1. 实现设备驱动的一个基本框架,包括注册设备设备的初始化,以及对设备进行操作等。 2. 开发人员不仅需要熟悉驱动程序开发技术,还需要了解Linux内核系统的相关知识,例如进程、中断、内存管理等。 3. 应该在代码注释中提供详细的文档,以方便其他开发人员进行维护和修改。 4. 在实现字符设备驱动过程中,必须保证安全性和可靠性,防止设备出现故障或者损坏用户的数据。 5. 在测试和维护设备驱动时,需要使用一些常见的工具和技术,例如devfs、udev等。 总之,Linux字符设备驱动开发是一个需要熟练技能和丰富经验的过程。开发人员需要有足够的专业知识和经验来确保设备驱动的高效和稳定性。通过精心设计和开发,Linux字符设备驱动可以提供高性能、高可靠性、易于使用的设备驱动,从而大大增强了Linux系统的功能和灵活性。 ### 回答3: Linux字符设备驱动开发Linux系统中的一个重要领域。其主要任务是开发一些支持字符设备的驱动程序,从而使用户能够在Linux系统中使用各种不同类型的字符设备,例如串口、打印机、卡器和磁盘等。同时,这些驱动程序还要保证设备完全可靠和高效地工作,确保系统的安全性和性能。 Linux字符设备驱动开发需要掌握以下基本知识: 1.了解Linux系统体系结构和内核架构 Linux系统由内核和用户空间组成,内核作为系统的核心组件,是实现系统功能的主要部分,因此了解内核体系结构和架构是开发Linux字符设备驱动所必须掌握的知识。 2.熟悉字符设备的相关知识 字符设备Linux系统中的一种重要的设备类型,它与其他类型设备不同之处在于它只能逐个字符地进行操作。因此需要深入了解字符设备的相关知识,例如驱动的主要功能、驱动程序与设备的交互方式、设备控制结构等。 3.熟练掌握C语言及Linux内核编程技术 编Linux字符设备驱动程序需要掌握良好的C语言编程知识以及熟练的Linux内核编程技术,包括内存管理、进程管理、文件系统、中断处理等。同时,还需要了解Linux内核代码的结构和代码的编规范,以便于编出符合内核标准的驱动程序。 4.掌握Linux驱动框架的使用方法 为了简化Linux驱动的开发流程,Linux提供了一些驱动框架,这些框架定义了一些驱动程序中常用的接口和函数,能够方便驱动程序的开发和调试。因此,Linux字符设备驱动开发者需要掌握其中的一些驱动框架,如字符驱动框架。 5.熟悉Linux字符设备驱动的开发过程 Linux字符设备驱动的开发过程主要包括驱动程序的初始化、驱动程序的主要功能实现、驱动程序的卸载等环节。在开发过程中,需要合理使用系统提供的工具和调试手段,如gdb、strace、make等,以便于分析和排查驱动程序出现的问题,确保驱动程序的稳定和可靠性。 总之,在Linux字符设备驱动开发过程中,开发者需要掌握相关的知识和技能,以实现对字符设备的编程和调试,开发出满足用户需求的高质量驱动程序。同时,Linux字符设备驱动开发也是一项长期持续的工作,开发者需要时刻关注最新的技术发展和硬件设备变化,才能更好地适应市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值