我是靠谱客的博主 细心故事,最近开发中收集的这篇文章主要介绍Linux 驱动开发 三十五:Linux 内核时钟管理一、概述二、高节拍率和低节拍率的优缺点三、Linux 内核时钟管理四、Linux 内核定时器五、Linux 内核定时器 API 函数六、Linux 内核短延时函数七、使用定时器步骤八、实验测试,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

参考:
linux时间管理,时钟中断,系统节拍_u010936265的博客-CSDN博客_系统节拍时钟中断
Linux内核时钟系统和定时器实现_anonymalias的专栏-CSDN博客_linux内核定时器实现
正点原子相关手册

一、概述

Linux 内核中有大量的函数需要时间管理,比如周期性的调度程序延时程序。实现思路如下:

1、硬件定时器提供时钟源(配置好定时器后,周期性触发定时中断)。

2、系统使用定时中断进行计时。

中断周期性产生的频率就是系统频率,也叫做节拍率。系统节拍率是可以设置的,单位是 Hz,我们在编译 Linux 内核的时候可以通过图形化界面 设置系统节拍率,按照如下路径打开配置界面:

-> Kernel Features
	-> Timer frequency (<choice> [=y])

选中 Timer frequency,打开以后如图下所示:
在这里插入图片描述
从上图可以看出,可选的系统节拍率为 100Hz200Hz250Hz300Hz500Hz1000Hz,默认情况下选择 100Hz。设置好以后打开 Linux 内核源码根目录下的 .config 文件,在 此文件中有如下图所示定义:
在这里插入图片描述
图中的 CONFIG_HZ100Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。打开文件 include/asm-generic/param.h,有如下内容:

#ifndef __ASM_GENERIC_PARAM_H
#define __ASM_GENERIC_PARAM_H

#include <uapi/asm-generic/param.h>

# undef HZ
# define HZ		CONFIG_HZ	/* Internal kernel timer frequency  内部内核定时器频率 */
# define USER_HZ	100		/* some user interfaces are  一些用户接口 */
# define CLOCKS_PER_SEC	(USER_HZ)       /* in "ticks" like times() */
#endif /* __ASM_GENERIC_PARAM_H */

7 行定义了一个宏 HZ,宏 HZ 就是 CONFIG_HZ,因此 HZ=100,我们后面编写 Linux 驱动的时候会常常用到 HZ,因为 HZ 表示一秒的节拍数,也就是频率

二、高节拍率和低节拍率的优缺点

高节拍率会提高系统时间精度,如果采用 100Hz 的节拍率,时间精度就是 10ms,采用 1000Hz 的话时间精度就是 1ms,精度提高了 10 倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数来说,能够以更高的精度运行,时间测量也更加准确。

高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,1000Hz100Hz 的系统节拍率相比,系统要花费 10 倍的 “精力” 去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用 1000Hz 的系统节拍率并不会增加太大的负载压力。

三、Linux 内核时钟管理

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

/* some arch's have a small-data section that can be accessed register-relative
 * but that can only take up to, say, 4-byte variables. jiffies being part of
 * an 8-byte variable may not be correctly accessed unless we force the issue
 */
#define __jiffy_data  __attribute__((section(".data")))

/*
 * The 64-bit value is not atomic - you MUST NOT read it
 * without sampling the sequence number in jiffies_lock.
 * get_jiffies_64() will do this for you as appropriate.
 */
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

jiffies_64 用于 64 位系统jiffies 用于 32 位系统为了兼容不同的硬件jiffies 其实就是 jiffies_64 的低 32 位,jiffies_64jiffies 的结构如下图所示:
在这里插入图片描述
当我们访问 jiffies 的时候其实访问的是 jiffies_64 的低 32 位,使用 get_jiffies_64 这个函数可以获取 jiffies_64 的值。在 32 位的系统上读取 jiffies 的值,在 64 位的系统上 jiffesjiffies_64 表示同一个变量,因此也可以直接读取 jiffies 的值。所以不管是 32 位的系统还是 64 位系统, 都可以使用 jiffies

前面说了 HZ 表示每秒的节拍数,jiffies 表示系统运行的 jiffies 节拍数,所以 jiffies/HZ 就是系统运行时间,单位为秒。

不管是 32 位还是 64 位的 jiffies,都有溢出的风险,溢出以后会重 新从 0 开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。假如 HZ 为最大 值 1000 的时候,32 位的 jiffies 只需要 49.7 天就发生了绕回,对于 64 位的 jiffies 来说大概需要 5.8 亿年才能绕回,因此 jiffies_64 的绕回忽略不计。处理 32jiffies 的绕回显得尤为重要, Linux 内核提供了如下表所示的几个 API 函数来处理绕回。
在这里插入图片描述
如果 unkown 超过 known 的话,time_after 函数返回真,否则返回假。如果 unkown 没有超过 known 的话 time_before 函数返回真,否则返回假。time_after_eq 函数和 time_after 函数类似, 只是多了判断等于这个条件。同理,time_before_eq 函数和 time_before 函数也类似。

1、溢出的处理思路

unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */

/* 判断有没有超时 */
if(time_before(jiffies, timeout)) {
    /* 超时未发生 */
} else {
    /* 超时发生 */
}

timeout 就是超时时间点,比如我们要判断代码执行时间是不是超过了 2 秒,那么超时时间 点就是 jiffies+(2*HZ),如果 jiffies 大于 timeout 那就表示超时了,否则就是没有超时。

2、Linux 内核时间转换函数

为了方便开发,Linux 内核提供了几个 jiffiesmsusns 之间的转换函数,如下表所示:
在这里插入图片描述

四、Linux 内核定时器

参考:
操作系统定时器原理分析(基于linux0.11) - 知乎 (zhihu.com)
Linux驱动开发-内核定时器-云社区-华为云 (huaweicloud.com)

内核定时器是内核用来控制在未来某个时间点(基于 jiffies (节拍总数))调度执行某个函数的一种机制。

当内核定时器定时时间到达时,会进入用户指定的函数,相当于软中断。内核定时器注册开启后,运行一次就不会再运行(相当于自动注销),我们可以重新设置定时器的超时时间,让定时器重复运行。

每当时钟中断发生时,全局变量 jiffies(一个 32 位的 unsigned long 变量)就加 1,因此 jiffies 记录了 linux 系统启动后时钟中断发生的次数,驱动程序常利用 jiffies 来计算不同事件间的时间间隔。内核每秒钟将 jiffies 变量增加 HZ 次。因此,对于 HZ 值为 100 的系统,jiffy+1 等于隔了 10ms,而对于 HZ1000 的系统,jiffy+1 仅为 1ms

Linux 内核使用 timer_list 结构体表示内核定时器,timer_list 定义在文件 include/linux/timer.h 中,定义如下:

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct list_head entry;				// 定时器链表的入口,用来将多个定时器来结成一跳双向循环链表。
	unsigned long expires;				// 设置超时时间,用jiffies作为基准值
	struct tvec_base *base;				// 定时器内补值,用户不要使用,用来指定此定时器在哪个CPU上执行

	void (*function)(unsigned long);	//类似中断服务函数,设置定时器到时后处理的函数
	unsigned long data;					//中断服务函数的参数

	int slack;

#ifdef CONFIG_TIMER_STATS
	int start_pid;
	void *start_site;
	char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

要使用内核定时器首先要先定义一个 timer_list 变量,表示定时器,tiemr_list 结构体的 expires 成员变量表示超时时间,单位为节拍数。

比如我们现在需要定义一个周期为 2 秒的定时 器,那么这个定时器的超时时间就是 jiffies+(2*HZ),因此 expires=jiffies+(2*HZ)function 就是 定时器超时以后的定时处理函数,我们要做的工作就放到这个函数里面,需要我们编写这个定 时处理函数。

五、Linux 内核定时器 API 函数

1、init_timer

init_timer 函数负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一定 要先用 init_timer 初始化一下。init_timer 函数原型如下:

函数原型#define init_timer(timer)
函数功能初始化定时器结构
函数参数timer:对应的定时器结构体
函数定义文件linux4.1.15includelinuxtimer.h

2、add_timer

add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行,函数原型如下

函数原型void add_timer(struct timer_list *timer)
函数功能向Linux内核注册定时器
函数参数timer:对应的定时器结构体
函数定义文件linux4.1.15includelinuxtimer.h

3、del_timer

del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出

函数原型int del_timer(struct timer_list * timer)
函数功能关闭定时器,停用一个定时器。
函数参数关闭定时器,停用一个定时器。
函数返回值返回0:成功
函数定义文件linux4.1.15includelinuxtimer.h

4、del_timer_sync

del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。

函数原型int del_timer_sync(struct timer_list *timer)
函数功能关闭定时器,停用一个定时器,多处理器使用。
如果编内核时不支持 SMP(多处理器), del_timer_sync()和 del_timer()等价
函数参数timer:对应的定时器结构体
函数返回值返回0:成功
函数定义文件linux4.1.15includelinuxtimer.h

5、mod_timer

mod_timer 函数用于修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器。

函数原型int mod_timer(struct timer_list *timer, unsigned long expires)
函数功能修改定时器超时时间
函数参数timer:对应的定时器结构体
expires:超时时间
函数返回值成功返回 :修改成功的时间值
函数定义文件linux4.1.15includelinuxtimer.h

六、Linux 内核短延时函数

有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。Linux 内核提供了毫秒、微秒和纳秒延时函数。

void ndelay(unsigned long nsecs);	// 纳秒级延时函数
void udelay(unsigned long usecs);	// 微妙级延时函数
void mdelay(unsigned long mseces);	// 毫秒级延时函数

上述延迟的实现原理本质上是忙等待,根据 CPU 频率进行一定次数的循环。在内核中,最好不要直接使用 mdelay() 函数, 这将无谓地耗费 CPU 资源。

void msleep(unsigned int msecs);							// 毫秒级延时函数
unsigned long msleep_interruptible(unsigned int msecs);		// 毫秒级延时函数
void ssleep(unsigned int seconds);							// 秒级延时函数

上述函数将使得调用它的进程睡眠参数指定的时间, msleep()ssleep()不能被打断,而 msleep_interruptible() 则可以被打断。

七、使用定时器步骤

1、定义定时器结构体 timer_list 变量

struct timer_list timer; /* 定义定时器 */

2、设置超时时间,定义定时器处理函数和传参

void timer_function(unsigned long arg)
{
    // 定时器处理代码
}

timer.function = timer_function;				/* 设置定时处理函数 */
timer.expires=jffies + msecs_to_jiffies(2000);	/* 超时时间 2 秒 */
timer.data = (unsigned long)&dev; 				/* 将设备结构体作为参数 */

3、开启定时器

init_timer(&mytimer);	/*初始化定时器*/
add_timer(&mytimer);	/*启动定时器*/

4、删除定时器

del_timer(&timer);
del_timer_sync(&timer);

八、实验测试

1、实验原理

在定时回调函数中实现 LED 灯闪烁。

2、设计思路

1、在驱动加载函数中初始化 LED 相关引脚,初始化定时器。

2、通过定时器回调函数控制 LED 灯亮和灭。

3、在驱动卸载函数删除定时器。

3、代码实现

1、工程配置

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/include",
                "/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include",
                "/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

2、Makefile

KERNELDIR := /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := timer.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

3、驱动源码

#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"
#include "asm/io.h"
#include "asm/uaccess.h"
#include "linux/of.h"
#include "linux/of_address.h"
#include "linux/of_gpio.h"
#include "linux/gpio.h"
#include "linux/timer.h"

#define NEWCHRDEV_MAJOR 0   /* 主设备号(如果为0则让系统自动分配,如果大于0则使用指定设备号) */
#define NEWCHRDEV_MINOR 0   /* 次设备号 */
#define NEWCHRDEV_COUNT 1   /* 设备号个数 */
#define NEWCHRDEV_NAME  "newchrdev" /* 名子 */

#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 */

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

typedef struct{
    struct cdev dev;        /* cdev 结构体 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    dev_t devid;            /* 设备号 */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    struct device_node *nd; /* 设备树节点 */
    int led_gpio;           /* led 所使用的 GPIO 编号 */
    struct timer_list timer;/* 定时器(软件) */
}newchrdev_t;

newchrdev_t newchrdev;

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int led_open(struct inode *inode, struct file *filp)
{
    printk("led_open!rn");
    filp->private_data = &newchrdev; /* 设置私有数据 */
    return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    printk("led_read!rn");
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
	unsigned char databuf[1];
	unsigned char ledstat;

    printk("led_write!rn");
	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!rn");
		return -EFAULT;
	}

	ledstat = databuf[0];		/* 获取状态值 */

	if(ledstat == LEDON) {	
		gpio_set_value(newchrdev.led_gpio,0);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		gpio_set_value(newchrdev.led_gpio,1);	/* 关闭LED灯 */
	}

    return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
    printk("led_release!rn");
	return 0;
}

static const struct file_operations newchrdevops = {
    .owner   = THIS_MODULE,
    .open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

void lq_timer_function(unsigned long arg)
{
    newchrdev_t *dev = (newchrdev_t*)arg;
    static int sta = 1;

    sta = !sta;
    gpio_set_value(dev->led_gpio,sta);
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(1000));
}

int lq_timer_init(newchrdev_t *dev)
{
    init_timer(&dev->timer);
    dev->timer.function = lq_timer_function;
    dev->timer.expires = jiffies + msecs_to_jiffies(1000);
    dev->timer.data = (unsigned long)dev;
    add_timer(&dev->timer);

    return 0;
}

int lq_timer_exit(newchrdev_t *dev)
{
    del_timer(&dev->timer);

    return 0;
}

/* 驱动入口函数 */
static int __init newchrdev_init(void)
{
    /* 驱动入口函数具体内容 */
    int ret;
    struct property;
    
    /* 1、字符设备号分配 */
    newchrdev.major = NEWCHRDEV_MAJOR;
    if(newchrdev.major){
        newchrdev.minor = NEWCHRDEV_MINOR;
        newchrdev.devid = MKDEV(newchrdev.major, newchrdev.minor);
        ret = register_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        printk("newchrdev.major > 0!rn");
    }else{
        ret = alloc_chrdev_region(&newchrdev.devid,0,NEWCHRDEV_COUNT,NEWCHRDEV_NAME);
        newchrdev.major = MAJOR(newchrdev.devid);
        newchrdev.minor = MINOR(newchrdev.devid);
        printk("newchrdev.major = 0!rn");
    }
    if(ret < 0){
        printk("newchrdev xxx_chrdev_region failed!rn");
        goto newchrdev_chrdev_region_failed;
    }
    printk("newchrdev devid = %d newchrdev major=%d,minor=%drn",newchrdev.devid,newchrdev.major,newchrdev.minor);

    /* 2、注册字符设备 */
    newchrdev.dev.owner = THIS_MODULE;
    cdev_init(&newchrdev.dev,&newchrdevops);
    ret = cdev_add(&newchrdev.dev,newchrdev.devid,NEWCHRDEV_COUNT);
    if(ret < 0){
        printk("newchrdev cdev_add failed!rn");
        goto newchrdev_cdev_add_failed;
    }

    /* 3、创建类 */
    newchrdev.class = class_create(THIS_MODULE,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.class)) {
        printk("newchrdev class_create failed!rn");
        goto newchrdev_class_create_failed;
    }

    /* 4、创建设备 */
    newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEWCHRDEV_NAME);
    if(IS_ERR(newchrdev.device)){
        printk("newchrdev device_create failed!rn");
        goto neschrdev_device_creat_failed;
    }

    /* 设置 LED 所使用的GPIO */
    /* [1]、获取设备节点 gpioled */
    newchrdev.nd = of_find_node_by_path("/gpioled");
    if(newchrdev.nd == NULL){
        printk("gpioled node cant not found!rn");
        goto neschrdev_device_creat_failed;
    }else{
        printk("gpioled node has been found!rn");
    }

    /* [2]、获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    newchrdev.led_gpio = of_get_named_gpio(newchrdev.nd, "led-gpio", 0);
    if(newchrdev.led_gpio < 0){
        printk("can't get led-gpio");
        goto neschrdev_device_creat_failed;
    }
    printk("led-gpio num = %drn", newchrdev.led_gpio);

    /* [3]、申请 IO */
    ret = gpio_request(newchrdev.led_gpio,"gpio-led");
    if(ret){
        printk("newchrdev gpio_request failed!rn");
        goto neschrdev_device_creat_failed;
    }

    /* [4]、设置 GPIO1_IO03 为输出 */
    ret = gpio_direction_output(newchrdev.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!rn");
        goto neschrdev_gpio_request_failed;
    }

    /*[5]、输出高电平,默认关闭 LED 灯*/
    gpio_set_value(newchrdev.led_gpio,1);

    lq_timer_init(&newchrdev);

    printk("newchrdev_init succed!rn");
    return 0;
neschrdev_gpio_request_failed:
    gpio_free(newchrdev.led_gpio);
neschrdev_device_creat_failed:
    class_destroy(newchrdev.class);
newchrdev_class_create_failed:
    cdev_del(&newchrdev.dev);
newchrdev_cdev_add_failed:
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);

newchrdev_chrdev_region_failed:   /* 字符设备号分配失败处理函数(未分配资源,因此不做处理) */
    printk("failed!rn");
    return ret;
}

/* 驱动卸载函数 */
static void __exit newchrdev_exit(void)
{
    /* 驱动卸载函数具体内容 */
    lq_timer_exit(&newchrdev);
    gpio_set_value(newchrdev.led_gpio,1);
    /* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);
    /* 5、释放IO*/
    gpio_free(newchrdev.led_gpio);
    /* 4、删除设备 */
    device_destroy(newchrdev.class,newchrdev.devid);
    /* 3、删除类 */
    class_destroy(newchrdev.class);
    /* 2、注销字符设备 */
    cdev_del(&newchrdev.dev);
    /* 1、释放设备号 */
    unregister_chrdev_region(newchrdev.devid,NEWCHRDEV_COUNT);
    printk("newchrdev_exit succed!rn");
}

module_init(newchrdev_init);
module_exit(newchrdev_exit);

MODULE_LICENSE("GPL");

4、测试

1、工程编译

onlylove@ubuntu:~/linux/driver/linux_driver/7_timer$ ls -al
total 28
drwxrwxr-x 3 onlylove onlylove 4096 Jan  2 18:17 .
drwxrwxr-x 9 onlylove onlylove 4096 Jan  2 06:34 ..
-rw-rw-r-- 1 onlylove onlylove  275 Jan  2 17:42 Makefile
-rw-rw-r-- 1 onlylove onlylove 9104 Jan  2 18:03 timer.c
drwxrwxr-x 2 onlylove onlylove 4096 Jan  2 17:47 .vscode
onlylove@ubuntu:~/linux/driver/linux_driver/7_timer$ make
make -C /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/onlylove/linux/driver/linux_driver/7_timer modules
make[1]: Entering directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
  CC [M]  /home/onlylove/linux/driver/linux_driver/7_timer/timer.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/onlylove/linux/driver/linux_driver/7_timer/timer.mod.o
  LD [M]  /home/onlylove/linux/driver/linux_driver/7_timer/timer.ko
make[1]: Leaving directory '/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'
onlylove@ubuntu:~/linux/driver/linux_driver/7_timer$ ls -al
total 116
drwxrwxr-x 4 onlylove onlylove  4096 Jan  2 18:17 .
drwxrwxr-x 9 onlylove onlylove  4096 Jan  2 06:34 ..
-rw-rw-r-- 1 onlylove onlylove   275 Jan  2 17:42 Makefile
-rw-rw-r-- 1 onlylove onlylove    65 Jan  2 18:17 modules.order
-rw-rw-r-- 1 onlylove onlylove     0 Jan  2 18:17 Module.symvers
-rw-rw-r-- 1 onlylove onlylove  9104 Jan  2 18:03 timer.c
-rw-rw-r-- 1 onlylove onlylove 10498 Jan  2 18:17 timer.ko
-rw-rw-r-- 1 onlylove onlylove   318 Jan  2 18:17 .timer.ko.cmd
-rw-rw-r-- 1 onlylove onlylove  2201 Jan  2 18:17 timer.mod.c
-rw-rw-r-- 1 onlylove onlylove  3628 Jan  2 18:17 timer.mod.o
-rw-rw-r-- 1 onlylove onlylove 20219 Jan  2 18:17 .timer.mod.o.cmd
-rw-rw-r-- 1 onlylove onlylove  7588 Jan  2 18:17 timer.o
-rw-rw-r-- 1 onlylove onlylove 26272 Jan  2 18:17 .timer.o.cmd
drwxrwxr-x 2 onlylove onlylove  4096 Jan  2 18:17 .tmp_versions
drwxrwxr-x 2 onlylove onlylove  4096 Jan  2 17:47 .vscode
onlylove@ubuntu:~/linux/driver/linux_driver/7_timer$

2、驱动加载

/ # ls
bin       etc       linuxrc   proc      sbin      timer.ko  usr
dev       lib       mnt       root      sys       tmp
/ # insmod timer.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
gpioled node has been found!
led-gpio num = 3
newchrdev_init succed!
/ #
/ #
/ # rmmod timer.ko
newchrdev_exit succed!
/ #
/ # insmod timer.ko
newchrdev.major = 0!
newchrdev devid = 260046848 newchrdev major=248,minor=0
gpioled node has been found!
led-gpio num = 3
newchrdev_init succed!
/ # random: nonblocking pool is initialized

/ #
/ # rmmod timer.ko
newchrdev_exit succed!
/ #

最后

以上就是细心故事为你收集整理的Linux 驱动开发 三十五:Linux 内核时钟管理一、概述二、高节拍率和低节拍率的优缺点三、Linux 内核时钟管理四、Linux 内核定时器五、Linux 内核定时器 API 函数六、Linux 内核短延时函数七、使用定时器步骤八、实验测试的全部内容,希望文章能够帮你解决Linux 驱动开发 三十五:Linux 内核时钟管理一、概述二、高节拍率和低节拍率的优缺点三、Linux 内核时钟管理四、Linux 内核定时器五、Linux 内核定时器 API 函数六、Linux 内核短延时函数七、使用定时器步骤八、实验测试所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(68)

评论列表共有 0 条评论

立即
投稿
返回
顶部