概述
参考:
linux时间管理,时钟中断,系统节拍_u010936265的博客-CSDN博客_系统节拍时钟中断
Linux内核时钟系统和定时器实现_anonymalias的专栏-CSDN博客_linux内核定时器实现
正点原子相关手册
一、概述
Linux
内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序。实现思路如下:
1、硬件定时器提供时钟源(配置好定时器后,周期性触发定时中断)。
2、系统使用定时中断进行计时。
中断周期性产生的频率就是系统频率,也叫做节拍率。系统节拍率是可以设置的,单位是 Hz
,我们在编译 Linux
内核的时候可以通过图形化界面 设置系统节拍率,按照如下路径打开配置界面:
-> Kernel Features
-> Timer frequency (<choice> [=y])
选中 Timer frequency
,打开以后如图下所示:
从上图可以看出,可选的系统节拍率为 100Hz
、200Hz
、250Hz
、300Hz
、500Hz
和 1000Hz
,默认情况下选择 100Hz
。设置好以后打开 Linux
内核源码根目录下的 .config
文件,在 此文件中有如下图所示定义:
图中的 CONFIG_HZ
为 100
,Linux
内核会使用 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
倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数来说,能够以更高的精度运行,时间测量也更加准确。
高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,1000Hz
和 100Hz
的系统节拍率相比,系统要花费 10
倍的 “精力” 去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用 1000Hz
的系统节拍率并不会增加太大的负载压力。
三、Linux 内核时钟管理
Linux
内核使用全局变量 jiffies
来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies
初始化为 0
,jiffies
定义在文件 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_64
和 jiffies
的结构如下图所示:
当我们访问 jiffies
的时候其实访问的是 jiffies_64
的低 32
位,使用 get_jiffies_64
这个函数可以获取 jiffies_64
的值。在 32
位的系统上读取 jiffies
的值,在 64
位的系统上 jiffes
和 jiffies_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
的绕回忽略不计。处理 32
位 jiffies
的绕回显得尤为重要, 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
内核提供了几个 jiffies
和 ms
、us
、ns
之间的转换函数,如下表所示:
四、Linux 内核定时器
参考:
操作系统定时器原理分析(基于linux0.11) - 知乎 (zhihu.com)
Linux驱动开发-内核定时器-云社区-华为云 (huaweicloud.com)
内核定时器是内核用来控制在未来某个时间点(基于 jiffies
(节拍总数))调度执行某个函数的一种机制。
当内核定时器定时时间到达时,会进入用户指定的函数,相当于软中断。内核定时器注册开启后,运行一次就不会再运行(相当于自动注销),我们可以重新设置定时器的超时时间,让定时器重复运行。
每当时钟中断发生时,全局变量 jiffies
(一个 32
位的 unsigned long
变量)就加 1
,因此 jiffies
记录了 linux
系统启动后时钟中断发生的次数,驱动程序常利用 jiffies
来计算不同事件间的时间间隔。内核每秒钟将 jiffies
变量增加 HZ
次。因此,对于 HZ
值为 100
的系统,jiffy+1
等于隔了 10ms
,而对于 HZ
为 1000
的系统,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_time
r 函数的同步版,会等待其他处理器使用完定时器再删除,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 内核短延时函数七、使用定时器步骤八、实验测试所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复