在IO模型中,I和O分别代表系统的输入和输出,在计算机中可以直观地理解为输入设备和输出设备,例如鼠标、键盘、显示器等。由于Linux中运行于用户空间的应用程序不能直接对硬件进行操作,需要应用程序向操作系统内核发起调用,将进程切换到内核空间,才能进行 IO 操作。IO 模型根据功能不同可划分为阻塞 IO、非阻塞 IO、信号驱动 IO,IO 多路复用和异步 IO。ioctl则是设备驱动程序中用来控制设备的接口函数,一个字符设备驱动通常需要实现设备的打开、关闭、读取、写入等功能,而在一些需要功能细分的场合下,就需要增设ioctl()命令来实现。
此次实验在之前定时器实验:《Linux驱动开发之使用内核定时器实现可重置的秒计时器》的基础上通过增加信号驱动IO模型和ioctl操作函数实现对定时器的控制和配置。
一、驱动程序编写
在上一次内核定时器实验的基础上,采用全新的原子变量操作函数,并通过添加信号驱动IO以及Poll轮询实现任意整数秒的秒计时器,最后添加ioctl操作函数实现对内核定时器的打开、关闭、设置等。
<头文件包含>
#define TIMER_OPEN _IO('L',0)
#define TIMER_CLOSE _IO('L',1)
#define TIMER_SET _IOW('L',2,int)
struct device_test{
dev_t dev_num;
int major ;
int minor ;
struct cdev cdev_test;
struct class *class;
struct device *device;
int counter;
int sec;
int flag;
struct fasync_struct *fasync;
};
DECLARE_WAIT_QUEUE_HEAD(read_wq); //定义并初始化等待队列头
atomic64_t v = ATOMIC_INIT(0);
static struct device_test dev1;
static void fnction_test(struct timer_list *t);
DEFINE_TIMER(timer_test,fnction_test);
void fnction_test(struct timer_list *t)
{
int add_num;
add_num = dev1.counter/1000;
atomic64_add_negative(add_num,&v); //原子变量增加相应计时值
dev1.sec = atomic64_read(&v);
dev1.flag=1;
wake_up_interruptible(&read_wq);
kill_fasync(&dev1.fasync,SIGIO,POLLIN);
mod_timer(&timer_test,jiffies_64 + msecs_to_jiffies(dev1.counter));
}
static int cdev_test_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;//设置私有数据
return 0;
}
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
if(copy_to_user(buf,&dev1.sec,sizeof(dev1.sec))){
printk("copy_to_user error \n");
return -1;
}
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)
{
file->private_data=&dev1;
return 0;
}
static long cdev_test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct device_test *test_dev = (struct device_test *)file->private_data;
switch(cmd){
case TIMER_OPEN:
dev1.sec = 0;
atomic64_set(&v,0);
add_timer(&timer_test);
printk("timer add successed\n");
break;
case TIMER_CLOSE:
dev1.sec = 0;
atomic64_set(&v,0);
del_timer(&timer_test);
printk("timer delete successed\n");
break;
case TIMER_SET:
test_dev->counter = arg;
timer_test.expires = jiffies_64 + msecs_to_jiffies(test_dev->counter);//设置定时时间
printk("timer set successed\n");
break;
default:
break;
}
return 0;
}
static __poll_t cdev_test_poll(struct file *file, struct poll_table_struct *p){
struct device_test *test_dev=(struct device_test *)file->private_data;
__poll_t mask=0;
poll_wait(file,&read_wq,p);
if (test_dev->flag == 1)
{
mask |= POLLIN;
}
return mask;
}
static int cdev_test_fasync (int fd, struct file *file, int on)
{
struct device_test *test_dev=(struct device_test *)file->private_data;
return fasync_helper(fd,file,on,&test_dev->fasync);
}
struct file_operations cdev_test_fops = {
.owner = THIS_MODULE, /
.open = cdev_test_open,
.read = cdev_test_read,
.release = cdev_test_release,
.unlocked_ioctl = cdev_test_ioctl,
.poll = cdev_test_poll,
.fasync = cdev_test_fasync,
};
static int __init timer_dev_init(void)
{
int ret;
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
if (ret < 0)
{
goto err_chrdev;
}
printk("alloc_chrdev_region is ok\n");
dev1.major = MAJOR(dev1.dev_num);
dev1.minor = MINOR(dev1.dev_num);
printk("major is %d \r\n", dev1.major);
printk("minor is %d \r\n", dev1.minor);
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_fops);
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if(ret<0)
{
goto err_chr_add;
}
dev1.class = class_create(THIS_MODULE, "test");
if(IS_ERR(dev1.class))
{
ret=PTR_ERR(dev1.class);
goto err_class_create;
}
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if(IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_device_create;
}
return 0;
err_device_create:
class_destroy(dev1.class); //删除类
err_class_create:
cdev_del(&dev1.cdev_test);
err_chr_add:
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
err_chrdev:
return ret;
}
static void __exit timer_dev_exit(void)
{
unregister_chrdev_region(dev1.dev_num, 1);
cdev_del(&dev1.cdev_test);
device_destroy(dev1.class, dev1.dev_num);
class_destroy(dev1.class);
printk("exit byebye\n");
}
module_init(timer_dev_init);
module_exit(timer_dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("tester");
二、测试程序编写
由于此次实验依旧没有涉及到硬件操作,为了使实验现象更明显,编写读取并打印定时器计时值的函数,通过信号驱动IO模型实现任意整数秒计时器秒数的打印。
<头文件包含>
int fd;
int count;
//信号处理函数
static void func(int signum)
{
read(fd,&count,sizeof(count));
printf("num is %d\n",count);
}
int main(int argc,char *argv[])
{
int flags;
fd = open("/dev/test",O_RDWR);
if (fd < 0)
{
perror("open error \n");
return fd;
}
signal(SIGIO,func); //注册信号处理函数
fcntl(fd,F_SETOWN,getpid());
flags = fcntl(fd,F_GETFD); //获取文件描述符标志
//开启信号驱动 IO ,打开 FASYNC 标志
fcntl(fd,F_SETFL,flags| FASYNC);
while(1);
close(fd);
return 0;
}
编写测试程序,通过ioctl操作函数实现对定时器的打开,关闭和设置。其中,设置定时器定时时间的输入参数单位为毫秒,定时间隔可以为任意整数。
<头文件包含>
#define TIME_OPEN _IO('L',0)
#define TIME_CLOSE _IO('L',1)
#define TIME_SET _IOW('L',2,int)
int main(int argc,char *argv[]){
int fd;
int ret;
fd = open("/dev/test",O_RDWR,0777);
if(fd < 0){
printf("file open error \n");
}
if(strcmp(argv[1],"timer_open") == 0)
{
ret = ioctl(fd,TIME_OPEN);
if(ret < 0){
printf("ioctl open error \n");
return -1;
}
return ret;
}
else if(strcmp(argv[1],"timer_set") == 0)
{
ret = ioctl(fd,TIME_SET,atoi(argv[2]));
if(ret < 0){
printf("ioctl set error \n");
return -1;
}
return ret;
}
else if(strcmp(argv[1],"timer_close") == 0)
{
ret = ioctl(fd,TIME_CLOSE);
if(ret < 0){
printf("ioctl close error \n");
return -1;
}
return ret;
}
close(fd);
}
三、运行测试
Makefile沿用之前编写好Makefile的即可,注意更改驱动文件名称。编译驱动程序及应用程序,得到驱动模块文件及可执行应用程序文件。
加载驱动并运行read_timer程序。
此时定时器还未启动,故没有计时器值的打印。设置定时器定时时间为1秒,打开定时器,观察read_timer打印现象。
可以看出,定时器成功实现了1秒时间间隔的计时。关闭定时器,重新设置定时器定时时间为3秒,打开定时器,观察read_timer打印现象。
现象表明,在定时器关闭之后,read_timer停止了打印。在重新开启定时器后,read_timer也重新开始打印,并且打印间隔时间为设定的3秒。关闭定时器并卸载驱动,使用dmesg查看内核打印信息。
内核打印信息与手动操作一一对应,证明该驱动运行完全正常。
总结:通过添加信号驱动IO模型实现了任意整数秒的秒计时器功能,并使用ioctl操作函数实现了对定时器的打开、关闭和设置。
标签:定时器,struct,int,dev1,timer,ioctl,cdev,Linux,test From: https://blog.csdn.net/woaidandanhou/article/details/142368281