最全Linux驱动开发八股文(九)

你好,我是拉依达。

这是我的Linux驱动开发八股文详细解析系列

本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。

全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料

现重新对内容进行整理,希望可以帮助到更多学习嵌入式的同学。

十、内核定时器

10.1 内核时间管理

内核必须管理系统的运行时间以及当前的日期和时间

硬件为内核提供了一个系统定时器用以计算流逝的时间, 系统定时器以某种频率自行触发时钟中断,该频率可以通过编程预定, 称节拍率

当时钟中断发生时, 内核就通过一种特殊中断处理程序对其进行处理。 内核知道连续两次时钟中断的间隔时间。 这个间隔时间称为节拍(tick) 。内核就是靠这种已知的时钟中断来计算墙上时间和系统运行时间。

节拍率

系统定时器频率是通过静态预处理定义的(HZ), 在系统启动时按照 Hz 对硬件进行设置。一般 ARM 体系结构的节拍率多数都等于 100。

在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率, 按照如下路径打开配置界面: -> Kernel Features -> Timer frequency ( [=y])

alt

Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。

# undef HZ
# define HZ CONFIG_HZ
# define USER_HZ 100
# define CLOCKS_PER_SEC (USER_HZ)

宏 HZ 就是 CONFIG_HZ,HZ=100,后面编写 Linux驱动的时候会常常用到 HZ,因为 HZ 表示一秒的节拍数,也就是频率

  • 高节拍率:优点
    • 提高系统时间精度,如果采用 100Hz 的节拍率,时间精度就是 10ms,采用1000Hz 的话时间精度就是 1ms。能够以更高的精度运行,时间测量也更加准确。
  • 高节拍率:缺点
    • 高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担, 1000Hz 和 100Hz的系统节拍率相比,系统要花费 10 倍的精力去处理中断。中断服务函数占用处理器的时间增加,需要根据实际情况,选择合适的系统节拍率。

jiffies

全局变量 jiffies 用来记录自系统启动以来产生的节拍的总数。 启动时, 内核将该变量初始化为 0, 每次时钟中断处理程序都会增加该变量的值。

因为一秒内时钟中断的次数等于 Hz, 所以 jiffes 一秒内增加的值为 Hz, 系统运行时间以秒为单位计算, 就等于time = jiffes/Hz( jiffes = seconds*HZ)

当 jiffies 变量的值超过它的最大存放范围后就会发生溢出, 对于 32 位无符号长整型, 最大取值为 2^32-1,在溢出前, 定时器节拍计数最大为 4294967295, 如果节拍数达到了最大值后还要继续增加的话, 它的值会回绕到 0

alt

  • 如果 unkown 超过 known 的话, time_after 函数返回真, 否则返回假。

  • 如果 unkown 没有超过 known的话 time_before 函数返回真, 否则返回假。

  • time_after_eq 函数在time_after上,多判断等于这个条件, time_before_eq 也类似

unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */
....
/* 判断有没有超时 */
if(time_before(jiffies, timeout)) 
{
    /* 超时未发生 */
} 
else 
{
    /* 超时发生 */
}

为了方便开发, Linux 内核提供了几个 jiffies 和 ms、 us、 ns 之间的转换函数

alt

  • 定时 10ms jiffies +msecs_to_jiffies(10)

  • 定时 10us jiffies +usecs_to_jiffies(10)

10.2 内核定时器

Linux 内核定时器采用系统时钟来实现,只需要提供超时时间(定时值)定时处理函数即可。、

内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

Linux 内核使用 timer_list 结构体表示内核定时器

struct timer_list
{
    struct list_head entry;
    unsigned long expires; /* 定时器超时时间, 单位是节拍数 */
    struct tvec_base *base;
    void (*function)(unsigned long); /* 定时处理函数指针 */
    unsigned long data; /* 要传递给 function 函数的参数 */
    int slack;
};

alt

alt

例 驱动层

......
#define CLOSE_CMD 		(_IO(0XEF, 0x1))	/* 关闭定时器 */
#define OPEN_CMD		(_IO(0XEF, 0x2))	/* 打开定时器 */
#define SETPERIOD_CMD	(_IO(0XEF, 0x3))	/* 设置定时器周期命令 */
......
/* timer设备结构体 */
struct timer_dev{
	......
	int timeperiod; 		/* 定时周期,单位为ms */
	struct timer_list timer;/* 定义一个定时器*/
	spinlock_t lock;		/* 定义自旋锁 */
};

struct timer_dev timerdev;	/* timer设备 */


static int timer_open(struct inode *inode, struct file *filp)
{
	......
	timerdev.timeperiod = 1000;		/* 默认周期为1s */
	ret = led_init();				/* 初始化LED IO */
	if (ret < 0) {
		return ret;
	}
	return 0;
}

//ioctl函数,和应用层ioctl对应,和open,close类似
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct timer_dev *dev =  (struct timer_dev *)filp->private_data;
	int timerperiod;
	unsigned long flags;
	
	switch (cmd) {
		case CLOSE_CMD:		/* 关闭定时器 */
			del_timer_sync(&dev->timer);
			break;
		case OPEN_CMD:		/* 打开定时器 */
			spin_lock_irqsave(&dev->lock, flags);
			timerperiod = dev->timeperiod;
			spin_unlock_irqrestore(&dev->lock, flags);
			mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));
			break;
		case SETPERIOD_CMD: /* 设置定时器周期 */
			spin_lock_irqsave(&dev->lock, flags);
			dev->timeperiod = arg;
			spin_unlock_irqrestore(&dev->lock, flags);
			mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));
			break;
		default:
			break;
	}
	return 0;
}

/* 设备操作函数 */
static struct file_operations timer_fops = {
	.owner = THIS_MODULE,
	.open = timer_open,
	.unlocked_ioctl = timer_unlocked_ioctl,
};

/* 定时器回调函数 */
void timer_function(unsigned long arg)
{
	struct timer_dev *dev = (struct timer_dev *)arg;
	static int sta = 1;
	int timerperiod;
	unsigned long flags;

	sta = !sta;		/* 每次都取反,实现LED灯反转 */
	gpio_set_value(dev->led_gpio, sta);
	
	/* 重启定时器 */
	spin_lock_irqsave(&dev->lock, flags);
	timerperiod = dev->timeperiod;
	spin_unlock_irqrestore(&dev->lock, flags);
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); 
 }

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init timer_init(void)
{
	......
	/* 6、初始化timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
	init_timer(&timerdev.timer);
	timerdev.timer.function = timer_function;
	timerdev.timer.data = (unsigned long)&timerdev;
	return 0;
}

static void __exit timer_exit(void)
{
	gpio_set_value(timerdev.led_gpio, 1);	/* 卸载驱动的时候关闭LED */
	del_timer_sync(&timerdev.timer);		/* 删除timer */
#if 0
	del_timer(&timerdev.tiemr);
#endif
	......
}

例 应用层

/* 命令值 */
#define CLOSE_CMD 		(_IO(0XEF, 0x1))	/* 关闭定时器 */
#define OPEN_CMD		(_IO(0XEF, 0x2))	/* 打开定时器 */
#define SETPERIOD_CMD	(_IO(0XEF, 0x3))	/* 设置定时器周期命令 */
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("Can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		printf("Input CMD:");
		ret = scanf("%d", &cmd);
		if (ret != 1) {				/* 参数输入错误 */
			gets(str);				/* 防止卡死 */
		}

		if(cmd == 1)				/* 关闭LED灯 */
			cmd = CLOSE_CMD;
		else if(cmd == 2)			/* 打开LED灯 */
			cmd = OPEN_CMD;
		else if(cmd == 3) {
			cmd = SETPERIOD_CMD;	/* 设置周期值 */
			printf("Input Timer Period:");
			ret = scanf("%d", &arg);
			if (ret != 1) {			/* 参数输入错误 */
				gets(str);			/* 防止卡死 */
			}
		}
		ioctl(fd, cmd, arg);		/* 控制定时器的打开和关闭 */	
	}
	close(fd);
}

#嵌入式##校招##八股文##Linux##linux驱动#
最全Linux驱动开发八股文 文章被收录于专栏

你好,我是拉依达。 这是我的Linux驱动开发八股文详细解析系列。 本系列最开始是我在csdn上更新的文章,目前已经是csdn搜索“linux驱动”综合推荐第一名,累计阅读次数4w次。 全文总字数近8w字,是目前全网最全面,最清晰的入门linux驱动学习资料。 现在我重新对内容进行整理,已专栏的形式发布在牛客上,希望可以帮助到更多学习嵌入式的同学。

全部评论

相关推荐

点赞 1 评论
分享
牛客网
牛客企业服务