前言
在Linux字符设备驱动中,ioctl是必须掌握一个函数,其实在软件层面它就是一个函数,但是我愿意称之为强大的硬件控制器!在应用中,让我深刻感受到了ioctl的魅力所在,既能够在软件层面实现应用层和驱动层的数据交互,也能够搭配一定的逻辑对硬件进行控制!
一、ioctl
long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:
filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
cmd:用来表示做的是哪一个操作
arg:和cmd配合用的参数
返回值:成功为0,失败-1
二、代码解析
2.1 驱动层
先来看一下头文件,下面是mychar.h中的头文件以及ioctl函数中需要用到的幻数,_IOR(MY_CHAR_MAGIC,1,int*)表示ioctl想要实现读操作。
#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
下面紧接着来看mychar.c中的内容:
#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*)
在mychar.h中包含以上内容,MY_CHAR_MAGIC表示’魔数’或者’幻数’,
宏定义MYCHAR_IOCTL_GET_MAXLEN使用在ioctl的第二个参数中,
_IOR(MY_CHAR_MAGIC,1,int*)的第一个参数是魔数,
第二个参数表示_IOR第几个控制方式
第三个参数表示ioctl操作的内核数据类型
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "mychar.h"
#define BUF_LEN 100
int major = 11; //主设备号
int minor = 0; //次设备号
int mychar_num = 1; //设备数量
struct mychar_dev
{
struct cdev mydev; //每一类设备都有一个cdev结构体
char mydev_buf[BUF_LEN]; //内核空间
int 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);
printk("mychar open is called!!!\n");
return 0;
}
ssize_t mychar_read(struct file *pfile, char __user *pbuf, size_t count, loff_t *ppos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if (count > pmydev->curlen)
{
size = pmydev->curlen;
}
else
{
size = count;
}
ret = copy_to_user(pbuf, pmydev->mydev_buf, size);
if (ret)
{
printk("copy_to_user failed!\n");
return -1;
}
memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size); //把在mydev_buf中剩下有效数据存放在以mydev_buf的首地址中
pmydev->curlen -= size; //读走的字节要被减去
return size;
}
ssize_t mychar_write(struct file *pfile, const char __user *pbuf, size_t count, loff_t *ppos)
{
int size = 0;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
if (count < BUF_LEN - pmydev->curlen)
{
size = count;
}
else
{
size = BUF_LEN - pmydev->curlen;
}
ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, pbuf, size);
if (ret)
{
printk("copy_from_user failed!\n");
return -1;
}
pmydev->curlen += size;
return size;
}
long mychar_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
int __user *pret = (int *)arg;
int maxlen = BUF_LEN;
int ret = 0;
struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
switch(cmd)
{
case MYCHAR_IOCTL_GET_MAXLEN:
ret = copy_to_user(pret, &maxlen, sizeof(int));
if (ret)
{
printk("fail to copy_to_user!\n");
return -1;
}
break;
case MYCHAR_IOCTL_GET_CURLEN:
ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
if (ret)
{
printk("fail to copy_from_user!\n");
return -1;
}
break;
default:
printk("the cmd is unknow!\n");
return -1;
}
return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{
printk("mychar clsoe is called!!!\n");
return 0;
}
/* 对字符设备的操作函数 */
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mychar_open,
.write = mychar_write,
.read = mychar_read,
.unlocked_ioctl = mychar_ioctl,
.release = mychar_close,
};
int __init mychar_init(void)
{
int ret = 0;
dev_t devno = MKDEV(major, minor); //组合设备号
ret = register_chrdev_region(devno, mychar_num, "mychar"); //手动申请设备号
if (ret) //返回值为0表示申请成功
{
ret = alloc_chrdev_region(&devno, 0, mychar_num, "mychar"); //申请失败则系统自动分配
if (ret)
{
printk("get devno failed!\n");
return -1;
}
major = MAJOR(devno); //从系统分配的设备号中取出主设备号
minor = MINOR(devno); //从系统分配的设备号中取出次设备号
devno = MKDEV(major, minor); //组合设备号
}
/* 使得设备具有myops中的函数操作方法 */
cdev_init(&gmydev.mydev, &myops);
gmydev.mydev.owner = THIS_MODULE;
/* 将设备号为devno的这个设备(mydev)添加到内核(内核hash链表中) */
cdev_add(&gmydev.mydev, devno, mychar_num);
printk("hello world!\n");
return 0;
}
void __exit mychar_exit(void)
{
dev_t devno = MKDEV(major, minor); //组合设备号
unregister_chrdev_region(devno, mychar_num); //注销设备号
/* 从内核中删除mydev这个设备 */
cdev_del(&gmydev.mydev);
printk("bye bye!!!\n");
}
MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);
2.2 应用层
以下代码的主要逻辑时:
1.以读写方式打开设备文件
2.使用ioctl从驱动层读取可以写入的最大字节数
3.将"hello"以字符串形式写入内核空间
4.使用ioctl从驱动层读取当前驱动层已经写入的字节数,也就是"hello"所占的6字节
5.最后读取并打印
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include "mychar.h"
int main(int argc, const char *argv[])
{
int fd = -1;
char buf[6];
int max = 0;
int cur = 0;
if (argc < 2)
{
printf("the arguement is too few!\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if(fd < 0)
{
printf("fail to open %s\n", argv[1]);
return -1;
}
ioctl(fd, MYCHAR_IOCTL_GET_MAXLEN, &max);
printf("max = %d\n", max);
write(fd, "hello", 6);
printf("max = %d\n", max);
ioctl(fd, MYCHAR_IOCTL_GET_CURLEN, &cur);
printf("cur = %d\n", cur);
read(fd, buf, 6);
printf("buf = %s\n", buf);
close(fd);
fd = -1;
return 0;
}
运行结果
总结
本期分享的主要是ioctl在驱动程序中的使用,这是一个非常重要的函数,希望各位小伙伴们不仅能使用,也能够理解其底层原理!最后,各位小伙伴们如果有收获,可以点赞收藏哦,你们的认可是我创作的动力,一起加油!