ioctl、printk及多个此设备支持

接上文:http://t.csdnimg.cn/Ai3GA(注册字符设备完整解析版)

对于修改全局变量后的代码注释:

int mychar_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));

   //已知成员的地址获得所在结构体变量的地址:container_of(成员地址,结构体类型名,成员在结构体中的名称)

   //当驱动程序接收到打开文件的请求(如通过其open方法)时,它有机会为新的file结构体分配一个private_data。这通常涉及到动态分配一个驱动程序特定的数据结构(如struct mychar_dev),并将其地址存储在file->private_data中。之后,每当对该文件执行其他操作(如读、写、关闭等)时,驱动程序都可以通过file->private_data访问到这个数据结构。

    printk("open call\n");
    return 0;
}

在后续操作函数中,需要说明使用的是哪个文件:

struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

一、ioctl操作实现

ioctl(Input/Output Control)是一个在 Unix-like 操作系统中的系统调用,用于控制设备或文件的各种操作。它允许用户空间程序与内核空间进行交互,执行一些特定的设备控制、状态查询或其他操作,而不必读写设备文件本身。

long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:

  •     filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
  •     cmd:用来表示做的是哪一个操作
  •     arg:和cmd配合用的参数

返回值:成功为0,失败-1

 mychar.c:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include "mychar.h"

//#define MYCHAR_DEV_CNT 3

//保存主设备号
int major = 11;
int minor = 0;
int mychar_num = 1;//MYCHAR_DEV_CNT;//次设备号数量

#define BUF_LEN 100
/*
struct cdev mydev;//创建的字符设备的描述信息
char mydev_buf[BUF_LEN];
int curlen = 0;//定义一个数据总是从下标0开始的长度(上面的mydev_buf)
*/

struct mychar_dev
{
	struct cdev mydev;
	char mydev_buf[BUF_LEN];
	int curlen;//curlen表示设备当前可读取的数据长度
};

struct mychar_dev gmydev;

int mychar_open(struct inode *pnode, struct file *pfile)
{
	pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));
	//已知成员的地址获得所在结构体变量的地址:container_of(成员地址,结构体类型名,成员在结构体中的名称)
	//当驱动程序接收到打开文件的请求(如通过其open方法)时,它有机会为新的file结构体分配一个private_data。这通常涉及到动态分配一个驱动程序特定的数据结构(如struct mychar_dev),并将其地址存储在file->private_data中。之后,每当对该文件执行其他操作(如读、写、关闭等)时,驱动程序都可以通过file->private_data访问到这个数据结构。
	printk("open call\n");
	return 0;
}

int mychar_release(struct inode *pnode, struct file *pfile)
{
	printk("release call\n");
	return 0;
}

ssize_t mychar_read(struct file *pfile, char __user *puser, size_t count, loff_t *p_pos)
{                   /*结构体表示当前打开的文件 
	 				指向用户空间缓冲区的指针,用于存储从设备读取的数据 
					希望从设备读取的字节数
					指向长偏移量的指针,用于指示在文件中的当前读写位置*/
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;//表示实际能读到的字节数
	int ret = 0;

	//如果期望读的字节数大于当前可以读到的字节数
	if(count > pmydev->curlen)
	{
		size = pmydev->curlen;//就把能读到的全都给实际能读到的size
	}
	else
	{
		size = count;//将buf里面未读完的数据往0位置放
	}
	/*用户空间中目标缓冲区的指针 ---内核空间中源数据的指针 ---要复制的字节数
	 * 用于从内核空间复制数据到用户空间*/
	ret = copy_to_user(puser,pmydev->mydev_buf,size);
	if(ret)
	{
		printk("copy_to_read failed\n");
		return -1;
	}
	memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
	//mydev_buf为内核储存地址,加上size后移到之前读完size大小数据的后面,将size大小后面没读完的数据重新放回0初始位置。拷贝的大小为(之前可读取的总长度curlen-已经读取的size大小)。
	//curlen = curlen - size;
	pmydev->curlen -= size;//然后将curlen往前移动size大小的字节即可回到0位置
	return size;//返回实际读的字节
}

//将用户空间的数据拷贝到内核空间,数据本身不变,+const
ssize_t mychar_write(struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos)
{
	//从puser将数据拷贝p_pos起始位置的期望写入字节数count大小字节给内核空间
	int size = 0;//表示实际能写入的字节数
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	if(count > BUF_LEN-pmydev->curlen)
	{
		size = BUF_LEN - pmydev->curlen;// 剩余空间不足以容纳全部请求的数据
	}
	else
	{
		size = count;// 剩余空间足够大,可以容纳全部请求的数据
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
	if(ret)
	{
		printk("copy_from_read failed\n");
		return -1;
	}
	pmydev->curlen  +=  size;// 更新文件位置
	return size;
}

long mychar_ioctl(struct file *pfile, unsigned int cmd,unsigned long arg)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	//文件指针 pfile、一个命令码 cmd 和一个参数 arg(用户空间地址)
	int __user *pret = (int *)arg;
	//arg 是一个指向用户空间 int 的指针
	int maxlen = BUF_LEN;
	int ret = 0;
	

	switch(cmd)
	{
	case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret,&maxlen,sizeof(int));
			if(ret)
			{
				printk(KERN_ERR"copy to user MAXLEN failed\n");
				return -EFAULT;
			}
			break;
	case MYCHAR_IOCTL_GET_CURLEN:
			ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
			if(ret)
			{
				printk(KERN_ERR"copy to user CURLEN failed\n");
				return -EFAULT;
			}
			break;
	default:
			printk(KERN_ERR"cmd unknow\n");
			return -EINVAL;//表示无效参数
	}
	return 0;
}


struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_release,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
};

int __init mychar_init(void)
{
	int ret = 0;
	
	//将主次设备号合成一个完整设备号
	dev_t devno = MKDEV(major,minor);
	//手动申请设备号,若返回值不为0,则自动设置
	ret = register_chrdev_region(devno,mychar_num,"qmycharq");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno,minor,mychar_num,"qmycharq");
		if(ret)
		{
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//自动分配的设备号不一定是11,所以需要分离出其主设备号
							 //因为次设备号都是从0开始,所以不用分离
	}

	cdev_init(&gmydev.mydev,&myops);//初始化字符设备(cdev)结构体
	/*在字符设备驱动的开发中,每个字符设备都需要一个与之对应的 cdev 结构体来表示。
	 * cdev_init 函数的作用就是初始化这个结构体,
	 * 并建立起 cdev 和 file_operations 结构体之间的连接
	 * 以便内核能够知道如何操作这个字符设备。*/

	gmydev.mydev.owner = THIS_MODULE;
	/*用于将字符设备(通过 cdev 结构体表示)的所有者设置为当前模块
	 * mydev 是指向 cdev 结构体的指针,而 THIS_MODULE 是一个宏,
	 * 它在编译时会被替换为指向当前模块 module 结构体的指针*/
	 /*如果驱动程序是作为模块加载的,它通常会使用 THIS_MODULE 宏来指代当前模块,并将 THIS_MODULE 赋值给设备信息的 owner 字段。这样,内核就能够知道哪个模块是设备的拥有者,并在需要时调用该模块的函数。*/

	cdev_add(&gmydev.mydev,devno,1);//将指定字符设备加入到管理内核的hash表中
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major,minor);
	cdev_del(&gmydev.mydev);
	//从内核中移除一个字符设备
	unregister_chrdev_region(devno,mychar_num);
	//参数:需要释放的设备号区域起始值,第一个设备号
	//第二个参数:制定释放设备号数量,即需要释放的设备号区域大小
	
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

mychar.h:

#ifndef MY_CHAR_H
#define MY_CHAR_H

#include <asm/ioctl.h>

#define MY_CHAR_MAGIC 'k'

#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)

#endif

testmychar_app.c:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>

#include "mychar.h"
int main(int argc,char *argv[])
{
	int fd = -1;
	char buf[32] = "";
	int max = 0;
	int cur = 0;

	if(argc < 2)
	{
		printf("The argument is too few\n");
		return -1;
	}

	fd = open(argv[1],O_RDWR);
	if(fd < 0)
	{
		printf("open %s failed\n",argv[1]);
		return 2;
	}

	ioctl(fd,MYCHAR_IOCTL_GET_MAXLEN,&max);
	printf("max len is %d\n",max);

	write(fd,"hello",6);
	
	ioctl(fd,MYCHAR_IOCTL_GET_CURLEN,&cur);
	printf("cur len is %d\n",cur);

	read(fd,buf,32);
	printf("buf=%s\n",buf);

	close(fd);
	fd = -1;
	return 0;
}

实现效果:

二、printk 

 //日志级别
#define    KERN_EMERG    "<0>"    /* system is unusable            */
#define    KERN_ALERT    "<1>"    /* action must be taken immediately    */
#define    KERN_CRIT    "<2>"    /* critical conditions            */
#define    KERN_ERR    "<3>"    /* error conditions            */

#define    KERN_WARNING    "<4>"    /* warning conditions            */

#define    KERN_NOTICE    "<5>"    /* normal but significant condition    */
#define    KERN_INFO    "<6>"    /* informational            */
#define    KERN_DEBUG    "<7>"    /* debug-level messages            */

用法:printk(KERN_INFO"....",....)
    
    printk(KERN_INFO"Hello World"); =====> printk("<6>""Hello World") ====> printk("<6>Hello World")
  
使用方法:dmesg --level=emerg,alert,crit,err,warn,notice,info,debug

三、多个次设备的支持

每一个具体设备(次设备不一样的设备),必须有一个struct cdev来代表它

cdev_init

cdev.owner赋值

cdev_add

以上三个操作对每个具体设备都要进行

multimychar.c:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include "mychar.h"

#define MYCHAR_DEV_CNT 3

//保存主设备号
int major = 11;
int minor = 0;
int mychar_num = MYCHAR_DEV_CNT;//次设备号数量

#define BUF_LEN 100

struct mychar_dev
{
	struct cdev mydev;
	char mydev_buf[BUF_LEN];
	int curlen;//curlen表示设备当前可读取的数据长度
};

struct mychar_dev gmydev_arr[MYCHAR_DEV_CNT];

int mychar_open(struct inode *pnode, struct file *pfile)
{
	pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mychar_dev,mydev));
	//已知成员的地址获得所在结构体变量的地址:container_of(成员地址,结构体类型名,成员在结构体中的名称)
	//当驱动程序接收到打开文件的请求(如通过其open方法)时,它有机会为新的file结构体分配一个private_data。这通常涉及到动态分配一个驱动程序特定的数据结构(如struct mychar_dev),并将其地址存储在file->private_data中。之后,每当对该文件执行其他操作(如读、写、关闭等)时,驱动程序都可以通过file->private_data访问到这个数据结构。
	printk("open call\n");
	return 0;
}

int mychar_release(struct inode *pnode, struct file *pfile)
{
	printk("release call\n");
	return 0;
}

ssize_t mychar_read(struct file *pfile, char __user *puser, size_t count, loff_t *p_pos)
{                   /*结构体表示当前打开的文件 
	 				指向用户空间缓冲区的指针,用于存储从设备读取的数据 
					希望从设备读取的字节数
					指向长偏移量的指针,用于指示在文件中的当前读写位置*/
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;//表示实际能读到的字节数
	int ret = 0;

	//如果期望读的字节数大于当前可以读到的字节数
	if(count > pmydev->curlen)
	{
		size = pmydev->curlen;//就把能读到的全都给实际能读到的size
	}
	else
	{
		size = count;//将buf里面未读完的数据往0位置放
	}
	/*用户空间中目标缓冲区的指针 ---内核空间中源数据的指针 ---要复制的字节数
	 * 用于从内核空间复制数据到用户空间*/
	ret = copy_to_user(puser,pmydev->mydev_buf,size);
	if(ret)
	{
		printk("copy_to_read failed\n");
		return -1;
	}
	memcpy(pmydev->mydev_buf,pmydev->mydev_buf + size,pmydev->curlen - size);
	//mydev_buf为内核储存地址,加上size后移到之前读完size大小数据的后面,将size大小后面没读完的数据重新放回0初始位置。拷贝的大小为(之前可读取的总长度curlen-已经读取的size大小)。
	//curlen = curlen - size;
	pmydev->curlen -= size;//然后将curlen往前移动size大小的字节即可回到0位置
	return size;//返回实际读的字节
}

//将用户空间的数据拷贝到内核空间,数据本身不变,+const
ssize_t mychar_write(struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos)
{
	//从puser将数据拷贝p_pos起始位置的期望写入字节数count大小字节给内核空间
	int size = 0;//表示实际能写入的字节数
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	if(count > BUF_LEN-pmydev->curlen)
	{
		size = BUF_LEN - pmydev->curlen;// 剩余空间不足以容纳全部请求的数据
	}
	else
	{
		size = count;// 剩余空间足够大,可以容纳全部请求的数据
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen,puser,size);
	if(ret)
	{
		printk("copy_from_read failed\n");
		return -1;
	}
	pmydev->curlen  +=  size;// 更新文件位置
	return size;
}

long mychar_ioctl(struct file *pfile, unsigned int cmd,unsigned long arg)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	//文件指针 pfile、一个命令码 cmd 和一个参数 arg(用户空间地址)
	int __user *pret = (int *)arg;
	//arg 是一个指向用户空间 int 的指针
	int maxlen = BUF_LEN;
	int ret = 0;
	

	switch(cmd)
	{
	case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret,&maxlen,sizeof(int));
			if(ret)
			{
				printk(KERN_ERR"copy to user MAXLEN failed\n");
				return -EFAULT;
			}
			break;
	case MYCHAR_IOCTL_GET_CURLEN:
			ret = copy_to_user(pret,&pmydev->curlen,sizeof(int));
			if(ret)
			{
				printk(KERN_ERR"copy to user CURLEN failed\n");
				return -EFAULT;
			}
			break;
	default:
			printk(KERN_ERR"cmd unknow\n");
			return -EINVAL;//表示无效参数
	}
	return 0;
}


struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_release,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
};

int __init mychar_init(void)
{
	int ret = 0;
	int i = 0;
	//将主次设备号合成一个完整设备号
	dev_t devno = MKDEV(major,minor);
	//手动申请设备号,若返回值不为0,则自动设置
	ret = register_chrdev_region(devno,mychar_num,"qmycharq");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno,minor,mychar_num,"qmycharq");
		if(ret)
		{
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//自动分配的设备号不一定是11,所以需要分离出其主设备号
							 //因为次设备号都是从0开始,所以不用分离
	}

	for(i=0;i<MYCHAR_DEV_CNT;i++)
	{
		devno = MKDEV(major,minor+i);
		cdev_init(&gmydev_arr[i].mydev,&myops);//初始化字符设备(cdev)结构体

		gmydev_arr[i].mydev.owner = THIS_MODULE;

		cdev_add(&gmydev_arr[i].mydev,devno,1);//将指定字符设备加入到管理内核的hash表中
	}
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major,minor);
	int i = 0;
	for(i=0;i<MYCHAR_DEV_CNT;i++)
	{
		cdev_del(&gmydev_arr[i].mydev);
		//从内核中移除一个字符设备
	}
	unregister_chrdev_region(devno,mychar_num);
	//参数:需要释放的设备号区域起始值,第一个设备号
	//第二个参数:制定释放设备号数量,即需要释放的设备号区域大小
	
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

Makefile:

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/fs4412/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)


modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else
CONFIG_MODULE_SIG=n
#obj-m += myhello.o

#obj-m += xyz.o
#xyz-objs = test.o func.o

obj-m += testparam.o
obj-m += mychar.o
obj-m += multimychar.o
endif

 运行结果:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值