驱动程序开发:Linux内核定时器与应用层ioctl的使用

目录

  • 知识简介
    • 一、 Linux内核定时器简介
      • 1、 内核定时器的时钟来源:
      • 2、 Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0, jiffies 定义在文件 include/linux/jiffies.h 中,定义如下:
      • 3、 内核定时器的程序描述
    • 二、 系统调用(实现用户空间对内核的操作)
      • 1、 Linux 应用程序对驱动程序的调用流程
      • 2、用户空间实现对内核的操作
  • 实验实践
    • 现象:

知识简介

一、 Linux内核定时器简介

1、 内核定时器的时钟来源:

硬件定时器提供时钟源(如晶振)->周期性的定时中断(用于计时)->中断周期性产生的频率(系统频率或系统节拍率)->系统时钟->内核定时器
设置系统时钟的时钟频率:
① 通过图形化界面(make menuconfig)设置系统节拍率:
-> Kernel Features
  -> Timer frequency ( [=y])
② Linux 内核源码根目录下的.config 文件也可以设置系统节拍率:
在这里插入图片描述

2、 Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0, jiffies 定义在文件 include/linux/jiffies.h 中,定义如下:

				示例代码 50.1.1.2 include/jiffies.h 文件代码段
76 extern u64 __jiffy_data jiffies_64;
77 extern unsigned long volatile __jiffy_data jiffies;

  由以上程序可知,jiffies 其实就是 jiffies_64 的低 32 位, jiffies_64 和 jiffies 的结构如图:
驱动程序开发:Linux内核定时器与应用层ioctl的使用_第1张图片
  jiffies 表示系统运行的 jiffies 节拍数,所以 jiffies/HZ 就是系统运行时间,单位为秒。
  Linux 内核提供了如下表所示的几个 API 函数来处理绕回。
驱动程序开发:Linux内核定时器与应用层ioctl的使用_第2张图片
  为了方便开发, Linux 内核提供了几个 jiffies 和 ms、 us、 ns 之间的转换函数,如下表所示:
驱动程序开发:Linux内核定时器与应用层ioctl的使用_第3张图片

3、 内核定时器的程序描述

  Linux 内核使用timer_list结构体表示内核定时器,timer_list定义在文件include/linux/timer.h 中,定义如下:
驱动程序开发:Linux内核定时器与应用层ioctl的使用_第4张图片
其相关的API函数:
初始化定时器:void init_timer(struct timer_list *timer)
注册的定时器:void add_timer(struct timer_list *timer)
删除的定时器:int del_timer(struct timer_list * timer)
删除的定时器(同步版):int del_timer_sync(struct timer_list *timer)
修改超时时间(定时值)的定时器:int mod_timer(struct timer_list *timer, unsigned long expires)


二、 系统调用(实现用户空间对内核的操作)

1、 Linux 应用程序对驱动程序的调用流程

如下图所示:
驱动程序开发:Linux内核定时器与应用层ioctl的使用_第5张图片

2、用户空间实现对内核的操作

  应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入” 到内核空间,这样才能实现对底层驱动的操作。 open、 close、 write 和 read 等这些函数是由 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。当我们调用 open 函数的时候流程如下图所示:
在这里插入图片描述
  每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合。例如:read、write、open、release、unlocked_ioctl等。
  这里我主要重点说明与ioctl相关的操作函数,unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应,其原型如下。

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int ioctl(int fd, unsigned long request, ...);

实验实践

  使用内核定时器周期性的点亮和熄灭开发板上的 LED 灯, LED 灯的闪烁周期由内核定时器来设置,测试应用程序可以控制内核定时器周期。
timer.c文件

/* 
 *  根据linux内核的程序查找所使用函数的对应头文件。 
 */  
#include    // MODULE_LICENSE,MODULE_AUTHOR  
#include      // module_init,module_exit  
#include    // printk  
#include        // struct file_operations  
#include      //kmalloc, kfree  
#include   // copy_to_user,copy_from_user  
#include        //ioremap,iounmap  
#include      //struct cdev,cdev_init,cdev_add,cdev_del  
#include    //class  
#include        //of_find_node_by_path  
#include   //of_get_named_gpio  
#include      //gpio_request,gpio_direction_output,gpio_set_value  
#include    //atomic_t  
#include     //timer_list
#include   //jiffies

/* 6.1 定义io_ctrl命令 */
/*
    参数1是幻数,参数2是序号,参数3是大小
    _IO(type,nr)
    _IOR(type,nr,size)
    _IOW(type,nr,size)
    _IOWR(type,nr,size)
 */
#define CLOSE_CMD	    (_IO(0XEF,0X1))        //关命令,关闭定时器
#define OPEN_CMD	    (_IO(0XEF,0X2))         //打开命令,打开定时器
#define SETPERIOD_CMD	(_IO(0XEF,0X3))    //设置定时器周期
  
/* 1.5 timer设备结构体 */  
struct timer_dev {  
    dev_t               devid;      /* 设备号 */  
    int                 major;      /* 主设备号 */  
    int                 minor;      /* 次设备号 */  
    char                *name;      /* 设备名称 */  
    int                 dev_count;  /* 设备个数 */  
    struct  cdev        cdev;       /* 注册设备结构体 */  
    struct  class       *class;     /* 类 */  
    struct  device      *device;    /* 设备 */  
    struct  device_node *nd;        /* 设备节点 */  
    int                 led_gpio;   /* led所使用的GPIO编号 */   
    struct timer_list   timer;      /* 定时器结构体 */
    int                 timerperiod;/* 定时器周期,单位ms */
    spinlock_t          lock;       /* 定义自旋锁 */
};  
struct timer_dev timerdev;             /* 定义timer设备结构体 */  
  
/* 
 * inode: 传递给驱动的inode 
 * filp : 要进行操作的设备文件(文件描述符) 
 * buf  : 数据缓冲区 
 * cnt  : 数据长度 
 * ppos : 相对于文件首地址的偏移 
 */  
/* 2.1 打开字符设备文件 */  
static int timer_open(struct inode *inode, struct file *filp) {  
	int ret = 0;  
	/* 设置私有类数据 */  
	filp->private_data = &timerdev;  
	return ret;  
}  
	  
/* 2.2 关闭字符设备文件 */  
static int timer_release(struct inode *inode, struct file *filp) {  
	int ret = 0;  
	return ret;  
}  
  
/* 2.3 向字符设备文件读出数据 */  
static ssize_t timer_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {  
    int ret = 0;  
    /* 提取私有属性 */  
    // struct timer_dev *dev = filp->private_data;  
    return ret;  
}  
  
/* 2.4 向字符设备文件写入数据 */  
static ssize_t timer_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) {  
    int ret = 0;  
    return ret;  
}  
  
/* 6.1 编写ioctl函数 */
/*
* @description : ioctl 函数,可与用户程序进行数据交互
* @param – filp : 要打开的设备文件(文件描述符)
* @param - cmd : 应用程序发送过来的命令
* @param - arg : 参数
* @return : 0 成功;其他 失败
*/
static long timer_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    int value = 0;      //保存用户输入进来的数据
    struct timer_dev *dev = filp->private_data; //获取私有属性
    unsigned long flags;    //暂存中断标志
    
	switch (cmd) {
        case CLOSE_CMD:     //关闭定时器
        /*del_timer_sync函数要完成的任务除了同del_timer一样从定时器队列中删除一个定时器对象外,
                        还会确保当函数返回时系统中没有任何处理器正在执行定时器对象上的定时器函数*/
            del_timer_sync(&dev->timer);
            break;
        case OPEN_CMD:      //重新加载打开定时器
            mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timerperiod)); //重新加载定时器
            break;
        case SETPERIOD_CMD: //,修改定时器的周期,重新加载打开定时器
            spin_lock_irqsave(&dev->lock,flags);    //上自旋锁,关闭本地中断
            ret = copy_from_user(&value, (int *)arg,sizeof(int));   //拷贝来在用户输入的数据
            if(ret < 0) {
                return -EFAULT;
            }
            dev->timerperiod = value;   //保存用户输入的数据
            spin_unlock_irqrestore(&dev->lock, flags);  //解锁,恢复中断
            mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timerperiod));
            break;
        default:
            break;
	}
    return ret;
}

/* 2.5 timer设备操作集 */  
static const struct file_operations timer_fops = {  
    .owner          =   THIS_MODULE,  
    .open           =   timer_open,  
    .release        =   timer_release,  
    .read           =   timer_read,  
    .write          =   timer_write,  
    .unlocked_ioctl =   timer_ioctl,
};  
  
/* 4.1 初始化LED灯 */
int led_init(struct timer_dev *dev) {
    int ret = 0;

    dev->nd = of_find_node_by_path("/gpioled"); //获取设备节点
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_nd;
    }
    dev->led_gpio = of_get_named_gpio(dev->nd,"led-gpio",0);    //获取节点中的GPIO属性
    if(dev->led_gpio < 0) {
        ret = -EINVAL;
        goto fail_gpio;      
    }
    ret = gpio_request(dev->led_gpio,"led-gpio");    //请求GPIO
    if(ret) {
        ret = -EBUSY;
        printk("IO %d cant't request!\r\n",dev->led_gpio);
        goto fail_request;
    }
    ret = gpio_direction_output(dev->led_gpio,1);   //输出方向为输出,高电平,默认关灯
    if(ret < 0) {
        ret = -EINVAL;
        goto fail_output;
    }
    return 0;

fail_output:
    gpio_free(dev->led_gpio);   //释放GPIO
fail_request:
fail_gpio:
fail_nd:
    return ret;

}

/* 5.2 定时器回调处理函数 */
/*
 *  此函数有点类似与定时器中断服务函数,每一次定时周期一到就进行该函数的调用
 *  unsigned long arg行参的传入值是sstruct timer_dev结构体
 */
static void timer_func(unsigned long arg) {
    struct timer_dev *dev = (struct timer_dev*)arg; //输入参数地址转换格式保存
    static int sta = 1; //led状态
    unsigned long flags;    //保存自旋锁的中断状态
    int timerperiod;      //定时器周期临时变量
    sta = !sta; //翻转
    
    /* spin_lock_irqsave在进入临界区前,会保存当前中断状态flag,关闭本地中断,然后进入临界区,在退出临界区时,把保存的flag写回到中断寄存器 */
    spin_lock_irqsave(&dev->lock, flags);   
    gpio_set_value(dev->led_gpio,sta);  //设置led的gpio输出电平
    timerperiod = dev->timerperiod; //获取用户输入的定时器的周期值
    spin_unlock_irqrestore(&dev->lock, flags);  //开锁,开启本地中断
    /* int mod_timer(struct timer_list *timer, unsigned long expires) */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timerperiod)); //重新加载定时器
}
  
/* 1.1 驱动模块入口函数 */  
static int __init timer_init(void) {  
    int ret = 0;  
    printk("timer_init!\r\n");  
    /***********************************************************************************************/  
    /* 3.1 配置timer结构体参数 */  
    timerdev.dev_count = 1;                  //设置设备号个数  
    timerdev.name  = "timer";                //设置设备名字  
    timerdev.major = 0;                      //0为系统分配设备号, 1为自定义设备号  
    if(timerdev.major) {  
        timerdev.devid = MKDEV(timerdev.major,0); //自定义主设备号和次设备号,并整合为设备号结构体中  
        ret = register_chrdev_region(timerdev.devid, timerdev.dev_count, timerdev.name);   //注册设备号  
    } else {  
        alloc_chrdev_region(&timerdev.devid, 0, timerdev.dev_count, timerdev.name);    //注册设备号,系统自动分配  
        timerdev.major = MAJOR(timerdev.devid);  
        timerdev.minor = MINOR(timerdev.devid);  
    }  
    if(ret < 0) {  
	    goto fail_devid;                //注册设备号失败  
	}  
    printk("timer major = %d, minor = %d\r\n",timerdev.major,timerdev.minor);   //注册设备号成功了,就打印主次设备号  
  
	/* 3.2 注册或者叫添加字符设备 */  
	cdev_init(&timerdev.cdev, &timer_fops);    //初始化cdev结构体  
	ret = cdev_add(&timerdev.cdev, timerdev.devid, timerdev.dev_count);    //添加字符设备  
	if(ret < 0) {  
	    goto fail_cdev;                 //注册或者叫添加设备失败  
	}  
	  
	/* 3.3 自动创建设备节点 */  
	timerdev.class = class_create(THIS_MODULE, timerdev.name);        //创建类  
	if(IS_ERR(timerdev.class)) {  
	    ret = PTR_ERR(timerdev.class);  
	    goto fail_class;  
    }  
    timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, timerdev.name); //创建设备  
    if(IS_ERR(timerdev.device)) {  
        ret = PTR_ERR(timerdev.device);  
        goto fail_device;  
    }  
    /***********************************************************************************************/  
    
    /* 4.2 LED灯初始化 */
    ret = led_init(&timerdev); 
    if(ret < 0) {
        goto fail_led_init;
    }

    /* 5.1 定时器 */
    spin_lock_init(&timerdev.lock);         //初始化自旋锁

    init_timer(&timerdev.timer);            //初始化定时器
    timerdev.timerperiod = 500;             //定时器周期500ms
    timerdev.timer.expires = jiffies + msecs_to_jiffies(timerdev.timerperiod);   //自定义毫秒的周期
    timerdev.timer.function = timer_func;   //添加定时器处理函数
    timerdev.timer.data = (unsigned long)&timerdev;  //传递数值给定时器函数
    add_timer(&timerdev.timer);            //添加定时器到系统中,只运行一次

    return 0;  

fail_led_init:  
fail_device:  
    class_destroy(timerdev.class);  
fail_class:  
    cdev_del(&timerdev.cdev);  
fail_cdev:  
    unregister_chrdev_region(timerdev.devid, timerdev.dev_count);  
fail_devid:  
    return ret;  
}  
	  
/* 1.2 驱动模块出口函数 */  
static void __exit timer_exit(void) {  
    /* 最后一一添加 , 注销字符设备驱动 */  
    gpio_set_value(timerdev.led_gpio,1);                            //关灯
    del_timer(&timerdev.timer);                                     //删除定时器
    gpio_free(timerdev.led_gpio);                                   //释放GPIO
    device_destroy(timerdev.class, timerdev.devid);                 //摧毁设备  
    class_destroy(timerdev.class);                                  //摧毁类  
    cdev_del(&timerdev.cdev);                                       //注销字符设备结构体  
    unregister_chrdev_region(timerdev.devid, timerdev.dev_count);   //注销设备号  
}  
	  
/* 1.3 注册驱动模块 */  
module_init(timer_init);  
module_exit(timer_exit);  
	  
/* 1.4 驱动许可和个人信息 */  
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("djw");  

timerAPP.c文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 * argc:应用程序参数个数 
 * argv[]:具体打参数内容,字符串形式 
 * ./timerAPP 
 * ./timerAPP /dev/timer
 */

#define CLOSE_CMD	    (_IO(0XEF,0X1))         //关命令,关闭定时器
#define OPEN_CMD	    (_IO(0XEF,0X2))         //打开命令,打开定时器
#define SETPERIOD_CMD	(_IO(0XEF,0X3))         //设置定时器周期

/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    unsigned int cmd;
    unsigned int arg;
    unsigned char str[100];

    /* 判断输入的元素个数 */
    if(argc != 2) {
        printf("ERROR USAGE!\r\n");
        return -1;
    }

    filename = argv[1];     //获取驱动文件的路径
    fd = open(filename,O_RDWR); //根据文件路径以读写方式打开文件
    if(fd < 0) {
        printf("file %s open failed!\r\n",filename);
        return -1;
    }

    /* 循环读取按键值 */
    while(1) {
        printf("Input CMD:");
        ret = scanf("%d",&cmd);
        if(ret != 1) {  //ret=1读取成功,ret=0读取失败
            gets(str);  //防止卡死
        }

        if(cmd == 1) {  //关闭定时器
            ioctl(fd,CLOSE_CMD,&arg);   //发送对应的指令
        } else if(cmd == 2) {
            ioctl(fd,OPEN_CMD,&arg);
        } else if(cmd == 3) {   //打开定时器
            printf("Inout Timer period:");
            ret = scanf("%d",&arg);
            if(ret != 1) {
                gets(str);
            }
            ioctl(fd,SETPERIOD_CMD,&arg);
        }
    }

    close(fd);
    return 0;
}

现象:

驱动程序开发:Linux内核定时器与应用层ioctl的使用_第6张图片

你可能感兴趣的:(Linux驱动开发,linux,驱动开发,arm)