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

你好,我是拉依达。

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

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

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

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

【下面是拉依达推荐学习相关专栏:】
一、Linux驱动学习专栏:拉依达的Linux驱动八股文 - 牛客网
二、Linux应用学习专栏:拉依达的Linux应用八股文 - 牛客网
【我的嵌入式学习和校招经验】 拉依达的嵌入式学习和秋招经验-CSDN博客
嵌入式学习规划/就业经验指导,可私信咨询

———————————————————————————————————————————————————

十、内核定时器

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驱动学习资料。 现在我重新对内容进行整理,已专栏的形式发布在牛客上,希望可以帮助到更多学习嵌入式的同学。

全部评论

相关推荐

一面(1h)没让自我介绍,对着写的简历评价了一遍选一个收获最大的项目讲一下我就挑了项目里面跟并行计算相关度最高的一个讲了一下(硬件加速相关)项目追问:阵列的结构,数据流怎么设计的blahblah然后直接撕代码,不过挺简单的,我的题是计算32bit输入中1的个数(当时没想写什么优化,就写了简单的移位+计数+中间寄存)对我写的代码分析,然后说我这样设计需要的周期太多了,以及寄存器资源有点浪费,有没有考虑过组合逻辑我就说按位for循环加起来,但这样会不会时序过不了,然后就顺势问时序过不了怎么解决,加法器和寄存器的资源哪个占用多之类的blahblah然后就是设计和验证的基础知识,具体问了哪些记不太清了(大概有setup/hold&nbsp;time,亚稳态,cdc,异步fifo的读写时钟,覆盖率...)聊了会天,主要是看我简历上写正在学uvm,问我怎么学的,有没有做验证的项目,有没有上培训班(都没有),然后说理解应届生验证方面的水平都差不多啥的反问,问了一下并行计算硬件组是做什么的,面试官说加速芯片、gpgpu之类的blahblah(具体的忘了)二面(30min)自我介绍(把每个项目的框架都说了一下,感觉自己介绍的有点久了,但面试官还是很耐心的听完了,泪目)同样也是让我说一个最有挑战性的项目,我也还是说了硬件加速那个,然后追问,问的比一面深一点点围绕这个项目问了很多,比如如果以这个项目做验证要考虑怎么搭建验证平台,考虑怎样的激励;怎样的设计是好设计;有没有设计过异步电路,异步电路设计相比于同步电路需要注意些什么之类的verilog是怎么学习的,sv怎么学习的,遇到了什么困难(当时说call&nbsp;back机制学的有点吃力,就顺势问了call&nbsp;back的知识)基础知识,sv的面向对象编程有哪些特征,虚函数的作用blahblah性格问题,问自己的优点和缺点是什么,现在是怎么克服这个缺点的反问,问了一下面试官带的团队是做哪方面的工作,回答说他带的团队中主要是做验证,也有设计的部分;然后问如果自己进入他的团队会做什么样的工作,面试官问我自己的倾向,我说自己更喜欢sv所以可能偏向验证一点。阿里平头哥25届秋招进行中!公司介绍:平头哥半导体有限公司于2018年9月宣布成立,是阿里巴巴集团的全资半导体芯片业务主体。平头哥拥有端云一体全栈产品系列,涵盖数据中心芯片、IoT芯片等,实现芯片端到端设计链路全覆盖。🗳【招聘对象】在2024年11月1日-2025年10月31日期间毕业的同学。中国大陆(内地)以毕业证为准,中国港澳台及海外地区以学位证为准。🗳【岗位方向】芯片前端:芯片设计/验证/DFT工程师、计算机体系结构工程师。芯片软件:芯片软件工程师、测试开发工程师、嵌入式软件工程师、编译器与计算机体系结构开发工程师、AI算法工程师。芯片平台:硬件开发工程师、模拟设计工程师、芯片物理设计工程师、信号完整性/电源完整性工程师、ATE测试工程师。📍【工作地点】上海、北京、深圳、杭州、成都等🗳【内推链接】https://recruitment.t-head.cn/campus/qrcode/home?code=0Rp91oftiBMXUQEaLJ6mDQ%3D%3D(免填内推码,直接点击链接投递)大家投递完可以在评论区打上姓名缩写+岗位,我来确认有没有内推成功喽
平头哥
|
校招
|
超多精选岗位
点赞 评论 收藏
分享
09-18 12:15
门头沟学院 Java
昌飞 工程技术员 16.5w+16w 硕士985
点赞 评论 收藏
分享
1 1 评论
分享
牛客网
牛客企业服务