Linux字符设备驱动程序的编写和测试

Linux字符设备驱动编写和测试





一、字符设备结构体

         字符设备驱动、块设备驱动和网络设备驱动作为linux内核三大驱动设备,字符设备主要完成字节的读写操作,常见的应用有鼠标、键盘等,结构体形式如下所示:

struct cdev{
	struct kobject kobj; //设备管理机制
	struct module *owner;//所说模块
	struct file_operations *ops;//字符设备操作方法
	struct list_head list; 
	dev_t dev;     //设备号
	unsigned int count;

}

         (1) cdev结构体中的dev_t表示32位的设备号,12位为主设备号,20位为次设备号,可通过宏定义MAJOR(dev_t dev)和MINOR(dev_t dev)从dev_t中获得主设备号和次设备号。此外,还可以使用宏定义MKDEV(int major, int minor)通过主设备号和次设备号生成dev_t。

         (2) Linux内核提供了一组函数对字符设备结构体进行操作,可用于操作cdev结构体。

void cdev_put(struct cdev *p); //struct cvde *p  表示设备信息的结构体 
void cdev_init(struct cdev *, struct file_operations *p); //用于初始化cdev的成员,并建立cdev和file_operation之间的连接
struct cdev *cdev_alloc(void);//用于动态申请一个cdev内存
int cdev_add(struct cdev *p, dev_t dev, unsigned count);//向系统添加一个cdev,完成字符设备的注册,对cdev_add()的调用通常发生在字符设备驱动模块加载函数中
void cdev_del(struct cdev *);//删除一个cdev,完成字符设备的注销,对cdev_del()函数的调用则通常发生在字符设备驱动模

         (3)调用register_chrdev_region()或alloc_chrdev_region()函数向系统申请设备号,再调用cdev_add()函数向系统注册字符设备。

int register_chrdev_region(dev_t from, unsigned count, const char *name); //已知起始设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);//位置起始设备号

         (4)file_operations结构体中的成员函数会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。

         llseek()可以修改一个文件当前的读写位置,并将新位置返回,出错时返回一个负值。

         read()从设备中读取数据,成功时返回读取的字节数,出错时返回一个负值。与用户空间应用程序中的ssize_t read(int fd,voidbuf,size_t count)和size_t fread(voidptr,size_t size,size_t nmemb,FILE*stream)是对应。

         write()向设备发送数据,成功时返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。与用户空间应用程序中的ssize_t write(int fd,constvoidbuf,size_t count)和size_t fwrite(const voidptr,size_t size,size_t nmemb,FILE*stream)是对应。

         read()和write()如果返回0,则表示end-of-file(EOF)。

         unlocked_ioctl()提供设备相关控制命令的实现(既不是读操作,也不是写操作),当调用成功时,返回给调用程序一个非负值。它与用户空间应用程序调用的int fcntl(int fd,int cmd,…/arg/)和intioctl(int d,int request,…)对应。

         mmap()将设备内存映射到进程的虚拟地址空间中,如果设备驱动未实现此函数,用户进行mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意义,帧缓冲被映射到用户空间后,应用程序可以直接访问它而无须在内核和应用间进行内存复制。它与用户空间应用程序中的voidmmap(voidaddr,size_t length,int prot,int flags,int fd,off_t offset)函数对应。

         (5)字符设备驱动模块加载与卸载函数如下:

static int __init char_test_init(void) 
static void __exit char_test_exit(void)

        在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中应实现设备号的释放和cdev的注销。



二、字符结构体编程

        在内核代码的…/drivers/ 目录下,新建一个globalmem文件夹,并在此目录下新建char_test.c 和相应的Makefile文件

char_test.c程序如下:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define char_test_SIZE 0x1000
#define MEM_CLEAR 0x1
#define char_test_MAJOR 230

static int char_test_major = char_test_MAJOR; //定义主设备号
module_param(char_test_major, int, S_IRUGO);//模块传参


struct char_test_dev{
	struct cdev cdev;//字符结构体
	unsigned char mem[char_test_SIZE];//使用内存
};

struct char_test_dev *char_test_devp;//申明char_test结构对象


//char_test设备驱动的读函数
static ssize_t char_test_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 char_test_dev *dev = filp->private_data;

	if (p >= char_test_SIZE)
		return 0;
	if (count > char_test_SIZE - p)
		count = char_test_SIZE - p;
	if (copy_to_user(buf, dev->mem + p, count)) {
		ret = -EFAULT;
	}
	else {
		*ppos += count;
		ret = count;
		printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
	}
	return ret;
}
//char_test设备驱动的写函数
static ssize_t char_test_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 char_test_dev *dev = filp->private_data;


	if (p >= char_test_SIZE)
		return 0;
	if (count > char_test_SIZE - p)
		count = char_test_SIZE - p;

	if (copy_from_user(dev->mem + p, buf, count))
		ret = -EFAULT;
	else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
	}
	return ret;
}
//寻址函数
static loff_t char_test_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	switch (orig) {
	case 0:
		if (offset< 0) {
			ret = -EINVAL;
			break;
		}
		if ((unsigned int)offset > char_test_SIZE) {
			ret = -EINVAL;
			break;
		}
		filp->f_pos = (unsigned int)offset;
		ret = filp->f_pos;
		break;
	case 1:
		if ((filp->f_pos + offset) > char_test_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;
}

static long char_test_ioctl(struct file *filp, unsigned int cmd,
	unsigned long arg)
{
	struct char_test_dev *dev = filp->private_data;
	switch (cmd) {
	case MEM_CLEAR:
		memset(dev->mem, 0, char_test_SIZE);
		printk(KERN_INFO "char_test is set to zero\n");
		break;
	default:
		return -EINVAL;
	}

	return 0;
}
//open函数
static int char_test_open(struct inode *inode, struct file *filp)
{
	filp->private_data = char_test_devp;
	return 0;
}


static const struct file_operations char_test_fops = {
	.owner = THIS_MODULE,
	.llseek = char_test_llseek,
	.read = char_test_read,
	.write = char_test_write,
	.unlocked_ioctl = char_test_ioctl,
	.open = char_test_open,
};


//字符设备加载函数
static void char_test_setup_cdev(struct char_test_dev *dev, int index)
{
	int err, devno = MKDEV(char_test_major, index);//获取设备结构体dev_t


	cdev_init(&dev->cdev, &char_test_fops);//初始化字符设备和字符设备处理方法
	dev->cdev.owner = THIS_MODULE;//初始化字符设备所属模块
	err = cdev_add(&dev->cdev, devno, 1);//添加一个字符设备
	if (err)
		printk(KERN_NOTICE "Error %d adding char_test%d", err, index);

}



//模块初始化
static int __init char_test_init(void) //初始化模块
{
	int ret;
	dev_t devno = MKDEV(char_test_major, 0);//获取字符设备结构体
	if (char_test_major)
		ret = register_chrdev_region(devno, 1, "char_test");//注册此cdev设备
	else {
		ret = alloc_chrdev_region(&devno, 0, 1, "char_test");//申请字符设备cdev空间
		char_test_major = MAJOR(devno);//获取主设备号
	}
	if (ret < 0)
		return ret;
	char_test_devp = kzalloc(sizeof(struct char_test_dev), GFP_KERNEL);//分配char_test结构体内存
	if (!char_test_devp) {
		ret = -ENOMEM;
		goto fail_malloc;
	}

	//主次设备的不同
	char_test_setup_cdev(char_test_devp, 0);
	return 0;
fail_malloc:
	unregister_chrdev_region(devno, 1);
	return ret;
}
module_init(char_test_init);


//模块卸载函数
static void __exit char_test_exit(void)
{
	cdev_del(&char_test_devp->cdev);
	kfree(char_test_devp);
	unregister_chrdev_region(MKDEV(char_test_major, 0), 1);
}
module_exit(char_test_exit);

MODULE_AUTHOR("Barry Song <baohua@kernel.org>");
MODULE_LICENSE("GPL v2");




 Makefile程序如下:

ifneq ($(KERNELRELEASE),)
obj-m := char_test.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	rm -fr .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif



三、字符设备加载

        ①、在char_test目录下输入make命令

        ②、以管理员身份插入模块,在char_test目录下输入insmod char_test.ko

        ③、可以通过查看主设备号开查看是否加载成功 : cat /proc/devices | grep char_test



四、测试

        ①在测试目录/home/xayf/xiaomu/test/char_dri(可自行设定)下新建测试程程序char_test.c如下:

#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
 
int  main()
{
   unsigned  char  buffer[200];
   unsigned  char  str[200] = "Hello xiaomu !";
  
   int  i = 0;
   int  fd;
   char  key=0;
   fd = open( "/home/xayf/xiaomu/test/char_dri/char_test" , O_RDWR);
   if  (fd == -1)
   {
       perror ( "open device button errr!" );
       return  0;
   }

   write(fd,str,100);

   read(fd,buffer,100);

   while(buffer[i] != '\0'){
       printf ( "%c" ,buffer[i]);
       ++i;
   }
   
   printf("\n");
   pause();
   close(fd);
   return  0;
}

        ②编译之后生成可执行文件 a.out
        ③在当前目录下新建文件夹:sudo mknod /home/xayf/xiaomu/test/char_dri/char_test c 230 0 (230是主设备号,可通过cat /proc/devices | grep char_test 查看)

        ④修改文件夹权限 sudo chmod -R 777 /home/xayf/xiaomu/test/char_dri/char_test

        ⑤运行可执行文件a.out进行验证,可以观察到在命令行中显示 Hello xiaomu !

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值