Linux学习第21天:Linux内核定时器驱动开发: 流淌的时间长河
Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长
在人类的发展进化中,时间是一个非常重要神秘的物质量。任何事物都是在时间的长河中流淌发生、发展、变化。我们进行驱动开发中对时间的定义和使用也是必须要掌握的重要知识点。
本节笔记主要学习Linux内核定时器的驱动开发,主要包括内核时间管理和定时器、硬件原理图分析【LED】、驱动开发和测试。最重要的内容为定时器驱动开发部分。
本笔记的脑图如下:
一、Linux时间管理和内核定时器
1.内核时间管理
作为一个应用者,不需要研究太深的具体实现。会用相应的API函数即可。
图形化配置界面可以设置系统节拍率。具体路径为:
Kernel Features
Timer frequency(<choice>[=y])
使用CONFIG_HZ来设置自己的系统时钟。
HZ表示一秒的节拍数,也就是频率。
使用jiffies来记录系统从启动以来的系统节拍数。
jiffies/HZ就是系统运行时间,单位为秒。
处理32位jiffies的绕回,用到的几个函数。如下:
函数 | 描述 |
time_after(unknown,known) | unkonwn超过known时为真 |
time_before(unknown,known) | unkonwn没有超过known时为真 |
time_after_eq(unknown,known) | unkonwn超过或等于known时为真 |
time_before_eq(unknown,known) | unkonwn没有超过或等于known时为真 |
2.内核定时器
需要周期性处理的工作都要用到定时器。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;
};
定义好定时器以后,需要以下函数对其进行初始化:
函数 | 说明 |
init_timer | 初始化 |
add_timer | 注册 |
del_timer | 删除 |
del_timer_sync | 等待其他处理器使用完定时器后删除 |
mod_timer | 修改定时值 |
内核定时器的使用流程如下:
1 struct timer_list timer; /* 定义定时器 */
2
3 /* 定时器回调函数 */
4 void function(unsigned long arg)
{
6 /*
7 * 定时器处理代码
8 */
9
10 /* 如果需要定时器周期性运行的话就使用 mod_timer
11 * 函数重新设置超时值并且启动定时器。
12 */
13 mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
14 }
15
16 /* 初始化函数 */
17 void init(void)
18 {
19 init_timer(&timer); /* 初始化定时器 */
20
21 timer.function = function; /* 设置定时处理函数 */
22 timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
23 timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
24
25 add_timer(&timer); /* 启动定时器 */
26 }
27
28 /* 退出函数 */
29 void exit(void)
30 {
31 del_timer(&timer); /* 删除定时器 */
32 /* 或者使用 */
33 del_timer_sync(&timer);
34 }
3.Linux内核短延时函数
函数 描述
void ndelay(unsigned long nsecs) 纳秒
void udelay(unsigned long usecs) 微秒
void mdelay(unsigned long mseces) 毫秒
二、硬件原理图分析
LED0 接到了 GPIO_3 上, GPIO_3 就是 GPIO1_IO03,当 GPIO1_IO03输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03的输出电平,输出 0 就亮,输出 1 就灭。
三、驱动程序开发
1.修改设备树文件
使用已有的即可。
2.定时器驱动程序开发
38 struct timer_dev{
39 dev_t devid; /* 设备号 */
40 struct cdev cdev; /* cdev */
41 struct class *class; /* 类 */
42 struct device *device; /* 设备 */
43 int major; /* 主设备号 */
44 int minor; /* 次设备号 */
45 struct device_node *nd; /* 设备节点 */
46 int led_gpio; /* key 所使用的 GPIO 编号 */
47 int timeperiod; /* 定时周期,单位为 ms */
48 struct timer_list timer; /* 定义一个定时器 */
49 spinlock_t lock; /* 定义自旋锁 */
50 };
定时器设备结构体,在 48 行定义了一个定时器成员变量 timer。
60 static int led_init(void)
61 {
62 int ret = 0;
63
64 timerdev.nd = of_find_node_by_path("/gpioled");
65 if (timerdev.nd== NULL) {
66 return -EINVAL;
67 }
68
69 timerdev.led_gpio = of_get_named_gpio(timerdev.nd ,"led-gpio",
0);
70 if (timerdev.led_gpio < 0) {
71 printk("can't get led\r\n");
72 return -EINVAL;
73 }
74
75 /* 初始化 led 所使用的 IO */
76 gpio_request(timerdev.led_gpio, "led"); /* 请求 IO */
77 ret = gpio_direction_output(timerdev.led_gpio, 1);
78 if(ret < 0) {
79 printk("can't set gpio!\r\n");
80 }
81 return 0;
82 }
LED 灯初始化函数,从设备树中获取 LED 灯信息,然后初始化相应的 IO。
91 static int timer_open(struct inode *inode, struct file *filp)
92 {
93 int ret = 0;
94 filp->private_data = &timerdev; /* 设置私有数据 */
95
96 timerdev.timeperiod = 1000; /* 默认周期为 1s */
97 ret = led_init(); /* 初始化 LED IO */
98 if (ret < 0) {
99 return ret;
100 }
101 return 0;
102 }
函数 timer_open,对应应用程序的 open 函数,应用程序调用 open 函数打开/dev/timer 驱动文件的时候此函数就会执行。此函数设置文件私有数据为 timerdev,并且初始化定时周期默认为 1 秒,最后调用 led_init 函数初始化 LED 所使用的 IO。
111 static long timer_unlocked_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
112 {
原子哥在线教学:www.yuanzige.com 论坛:www.openedv.com
1228
I.MX6U 嵌入式 Linux 驱动开发指南
113 struct timer_dev *dev = (struct timer_dev *)filp->private_data;
114 int timerperiod;
115 unsigned long flags;
116
117 switch (cmd) {
118 case CLOSE_CMD: /* 关闭定时器 */
119 del_timer_sync(&dev->timer);
120 break;
121 case OPEN_CMD: /* 打开定时器 */
122 spin_lock_irqsave(&dev->lock, flags);
123 timerperiod = dev->timeperiod;
124 spin_unlock_irqrestore(&dev->lock, flags);
125 mod_timer(&dev->timer, jiffies +
msecs_to_jiffies(timerperiod));
126 break;
127 case SETPERIOD_CMD: /* 设置定时器周期 */
128 spin_lock_irqsave(&dev->lock, flags);
129 dev->timeperiod = arg;
130 spin_unlock_irqrestore(&dev->lock, flags);
131 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));
132 break;
133 default:
134 break;
135 }
136 return 0;
137 }
函数 timer_unlocked_ioctl,对应应用程序的 ioctl 函数,应用程序调用 ioctl函数向驱动发送控制信息,此函数响应并执行。此函数有三个参数: filp, cmd 和 arg,其中 filp是对应的设备文件, cmd 是应用程序发送过来的命令信息, arg 是应用程序发送过来的参数,在本章例程中 arg 参数表示定时周期。
CLOSE_CMD: 关闭定时器命令, 调用 del_timer_sync 函数关闭定时器。
OPEN_CMD:打开定时器命令,调用 mod_timer 函数打开定时器,定时周期为 timerdev 的
timeperiod 成员变量,定时周期默认是 1 秒。
SETPERIOD_CMD:设置定时器周期命令,参数 arg 就是新的定时周期,设置 timerdev 的
timeperiod 成员变量为 arg 所表示定时周期指。并且使用 mod_timer 重新打开定时器,使定时器
以新的周期运行。
139 /* 设备操作函数 */
140 static struct file_operations timer_fops = {
141 .owner = THIS_MODULE,
142 .open = timer_open,
143 .unlocked_ioctl = timer_unlocked_ioctl,
144 };
定时器驱动操作函数集 timer_fops。
146 /* 定时器回调函数 */
147 void timer_function(unsigned long arg)
148 {
149 struct timer_dev *dev = (struct timer_dev *)arg;
150 static int sta = 1;
151 int timerperiod;
152 unsigned long flags;
153
154 sta = !sta; /* 每次都取反,实现 LED 灯反转 */
155 gpio_set_value(dev->led_gpio, sta);
156
157 /* 重启定时器 */
158 spin_lock_irqsave(&dev->lock, flags);
159 timerperiod = dev->timeperiod;
160 spin_unlock_irqrestore(&dev->lock, flags);
161 mod_timer(&dev->timer, jiffies +
msecs_to_jiffies(dev->timeperiod));
162 }
函数 timer_function,定时器服务函数,此函有一个参数 arg,在本例程中arg 参数就是 timerdev 的地址,这样通过 arg 参数就可以访问到设备结构体。当定时周期到了以后此函数就会被调用。在此函数中将 LED 灯的状态取反,实现 LED 灯闪烁的效果。因为内核定时器不是循环的定时器,执行一次以后就结束了,因此在 161 行又调用了 mod_timer 函数重新开启定时器。
169 static int __init timer_init(void)
170 {
171 /* 初始化自旋锁 */
172 spin_lock_init(&timerdev.lock);
173
174 /* 注册字符设备驱动 */
175 /* 1、创建设备号 */
176 if (timerdev.major) { /* 定义了设备号 */
177 timerdev.devid = MKDEV(timerdev.major, 0);
178 register_chrdev_region(timerdev.devid, TIMER_CNT,
TIMER_NAME);
179 } else { /* 没有定义设备号 */
180 alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT,
TIMER_NAME);
181 timerdev.major = MAJOR(timerdev.devid); /* 获取主设备号 */
182 timerdev.minor = MINOR(timerdev.devid); /* 获取次设备号 */
183 }
184
185 /* 2、初始化 cdev */
186 timerdev.cdev.owner = THIS_MODULE;
187 cdev_init(&timerdev.cdev, &timer_fops);
188
189 /* 3、添加一个 cdev */
190 cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);
191
192 /* 4、创建类 */
193 timerdev.class = class_create(THIS_MODULE, TIMER_NAME);
194 if (IS_ERR(timerdev.class)) {
195 return PTR_ERR(timerdev.class);
196 }
197
198 /* 5、创建设备 */
199 timerdev.device = device_create(timerdev.class, NULL,
timerdev.devid, NULL, TIMER_NAME);
200 if (IS_ERR(timerdev.device)) {
201 return PTR_ERR(timerdev.device);
202 }
203
204 /* 6、初始化 timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
205 init_timer(&timerdev.timer);
206 timerdev.timer.function = timer_function;
207 timerdev.timer.data = (unsigned long)&timerdev;
208 return 0;
209 }
第 169~209 行,函数 timer_init,驱动入口函数。在第 205~207 行初始化定时器,设置定时器的定时处理函数为 timer_function,另外设置要传递给 timer_function 函数的参数为 timerdev的地址。在此函数中并没有调用 timer_add 函数来开启定时器,因此定时器默认是关闭的,除非应用程序发送打开命令。
216 static void __exit timer_exit(void)
217 {
218
219 gpio_set_value(timerdev.led_gpio, 1); /* 卸载驱动的时候关闭 LED */
220 del_timer_sync(&timerdev.timer); /* 删除 timer */
221 #if 0
222 del_timer(&timerdev.tiemr);
223 #endif
224
225 /* 注销字符设备驱动 */
226 cdev_del(&timerdev.cdev); /* 删除 cdev */
227 unregister_chrdev_region(timerdev.devid, TIMER_CNT);
228
229 device_destroy(timerdev.class, timerdev.devid);
230 class_destroy(timerdev.class);
231 }
驱动出口函数,在 219 行关闭 LED,也就是卸载驱动以后 LED 处于熄灭状态。第 220 行调用 del_timer_sync 函数删除定时器,也可以使用 del_timer 函数。
3.编写测试APP
21 /* 命令值 */
22 #define CLOSE_CMD (_IO(0XEF, 0x1)) /* 关闭定时器 */
23 #define OPEN_CMD (_IO(0XEF, 0x2)) /* 打开定时器 */
24 #define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 设置定时器周期命令 */
25
26 /*
27 * @description : main 主程序
28 * @param - argc : argv 数组元素个数
29 * @param - argv : 具体参数
30 * @return : 0 成功;其他 失败
31 */
32 int main(int argc, char *argv[])
33 {
34 int fd, ret;
35 char *filename;
36 unsigned int cmd;
37 unsigned int arg;
38 unsigned char str[100];
39
40 if (argc != 2) {
41 printf("Error Usage!\r\n");
42 return -1;
43 }
44
45 filename = argv[1];
46
47 fd = open(filename, O_RDWR);
48 if (fd < 0) {
49 printf("Can't open file %s\r\n", filename);
50 return -1;
51 }
52
53 while (1) {
54 printf("Input CMD:");
55 ret = scanf("%d", &cmd);
56 if (ret != 1) { /* 参数输入错误 */
57 gets(str); /* 防止卡死 */
58 }
59
60 if(cmd == 1) /* 关闭 LED 灯 */
61 cmd = CLOSE_CMD;
62 else if(cmd == 2) /* 打开 LED 灯 */
63 cmd = OPEN_CMD;
64 else if(cmd == 3) {
65 cmd = SETPERIOD_CMD; /* 设置周期值 */
66 printf("Input Timer Period:");
67 ret = scanf("%d", &arg);
68 if (ret != 1) { /* 参数输入错误 */
69 gets(str); /* 防止卡死 */
70 }
71 }
72 ioctl(fd, cmd, arg); /* 控制定时器的打开和关闭 */
73 }
74 close(fd);
75 }
①、运行 APP 以后提示我们输入要测试的命令,输入 1 表示关闭定时器、输入 2 表示打开定时器,输入 3 设置定时器周期。
②、如果要设置定时器周期的话,需要让用户输入要设置的周期值,单位为毫秒。
第 22~24 行,命令值。
第 53~73 行, while(1)循环,让用户输入要测试的命令,然后通过第 72 行的 ioctl 函数发送给驱动程序。如果是设置定时器周期命令 SETPERIOD_CMD,那么 ioctl 函数的 arg 参数就是用户输入的周期值。
四、运行测试
1.编译驱动程序和测试AP
编译成功以后就会生成一个名为“ timer.ko”的驱动模块文件。
编译成功以后就会生成 timerApp 这个应用程序。
2.运行测试
根据提示输入指令及周期,观察LED的交替亮变化即可。
五、总结
本节笔记主要学习Linux内核定时器的驱动开发,主要包括内核时间管理和定时器、硬件原理图分析【LED】、驱动开发和测试。最重要的内容为定时器驱动开发部分。
本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。
相关文章:

Linux学习第21天:Linux内核定时器驱动开发: 流淌的时间长河
Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 在人类的发展进化中,时间是一个非常重要神秘的物质量。任何事物都是在时间的长河中流淌发生、发展、变化。我们进行驱动开发中对时间的定义和使用也是…...
Centos服务在服务器重启后自启
以Dolphin为例 打开rc.local文件以编辑: sudo vi /etc/rc.d/rc.local在文件中添加您的启动命令。在您的情况下,要添加的命令如下: sh /opt/dolphinscheduler/zookeeper/bin/zkServer.sh start sh /opt/dolphinscheduler/dolphinscheduler/…...

慢性疼痛治疗服务公司Kindly MD申请700万美元纳斯达克IPO上市
来源:猛兽财经 作者:猛兽财经 猛兽财经获悉,慢性疼痛治疗服务公司Kindly MD近期已向美国证券交易委员会(SEC)提交招股书,申请在纳斯达克IPO上市,股票代码为(KDLY),Kindly MD计划通过…...

代码随想录 Day6 哈希 LeetcodeT454 四数之和II T383赎金信 T15 三数之和 T18 四数之和
本文代码思路来源于: 代码随想录 前言 希望大家在刷这部分题的时候先熟悉熟悉哈希结构的基本常用api,比较方便理解. LeetCode T454 四数之和 题目链接:454. 四数相加 II - 力扣(LeetCode) 题目思路 暴力解法仍然是遍历四个数组解决此题, 哈希的思路有…...

干货速来|教你如何撰写毕业论文
撰写毕业论文对于正常大学毕业至关重要。毕业论文是对学生在大学期间所学知识的综合运用和深入研究的体现,也是对学术能力和独立思考能力的考验。 撰写毕业论文的过程需要学生投入大量的时间和精力,包括选题、文献综述、研究方法选择、数据收集和分析、…...

【ROS 2】-2 话题通信
飞书原文链接: Docs...

Unity之NetCode多人网络游戏联机对战教程(2)--简单实现联机
文章目录 1.添加基本组件2.创建NetworkManager组件3.创建Player4.创建地面5.创建GameManager6.编译运行7. 测试联机后话 1.添加基本组件 NetworkManagerPlayerScene 2.创建NetworkManager组件 创建一个空物体,命名为NetworkManager 选择刚刚创建的NetworkManager…...
makdown文法
这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…...

新手程序员怎么接单?
程序员如何在自己年富力强的时候,最大化发挥自己的能力?将超能力转化为“钞能力”? 有人还在苦哈哈当老黄牛,一身使不完的牛劲,有人已经另辟蹊径,开创了自己的一片致富小天地。 接单找兼职,就…...

接口测试——接口协议抓包分析与mock_L2
目录: 抓包工具charles抓包工具fiddler抓包工具证书配置app抓包实战练习接口测试实战练习 1.抓包工具charles 工具介绍 支持 SSL 代理支持流量控制支持重发网络请求,方便后端调试支持修改网络请求参数支持网络请求的截获并动态修改可以自动将 json 或…...

Seata入门系列【1】安装seata 1.7.1+nacos 2.1.1
1 介绍 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。 Github: https://github.com/seata/seata 官方文档:h…...

2023年职业院校技能大赛中职组----大数据应用与服务赛项任务书试题
2023年职业院校技能大赛中职组----大数据应用与服务赛项任务书试题 模块一:数据库系统运维(25分)任务一:数据库系统搭建(10分)任务二:房源数据库系统运维(15分) 模块二&a…...
产品经理的职业前景怎么样?一文为你全面解答!
随着科技的迅速发展和市场竞争的日益激烈,产品经理这个职业变得越来越炙手可热。产品经理负责一款产品的全生命周期管理,从需求收集到设计、开发、测试、发布,再到市场推广和用户反馈,都需要产品经理参与决策。因此,这…...

【深度学习】图像去噪(2)——常见网络学习
【深度学习】图像去噪 是在 【深度学习】计算机视觉 系列文章的基础上,再次针对深度学习(尤其是图像去噪方面)的基础知识有更深入学习和巩固。 1 DnCNN 1.1 网络结构 1.1.1 残差学习 1.1.2 Batch Normalization (BN) 1.1.2.1 背景和目标…...

八大排序详解
目录 1.排序的概念及应用 1.1 排序的概念 1.2 排序的应用 1.3 常见的排序算法 2.常见排序算法的实现 2.1 直接插入排序 2.1.1 基本思想 2.1.2 动图解析 2.1.3 排序步骤(默认升序) 2.1.4 代码实现 2.1.5 特性总结 2.2 希尔排序 2.2.1 基本思…...

自定义热加载:如何不停机实现核心代码更新
文章目录 1. 常见的几种实现代码热更新的几种方式对于开发环境我们可以使用部署环境1. 使用 Arthas 的 redefine 命令来加载新的 class 文件2. 利用 URLClassLoader 动态加载3. 通过Java的Instrumentation API 也是可以实现的 2. 实现1. ClassScanner扫描目录和加载类2. 定时任…...

Spring Cloud Alibaba Nacos 2.2.3 (2) - 单机版启动 (winodows 和 linux )
Nacos 2.2.3 (1) - 下载与数据库配置 参考下载与数据库配置 启动服务器 执行 nacos-server-2.2.3\bin 下的startup.sh或者startup.cmd (根据不同系统) windows 下nacos 单机启动 方式一: 1,打开cmd 2,cd 到nacos-s…...

VB从资源文件中播放wav音乐文件
Private Const SND_SYNC &H0 Private Const SND_MEMORY &H4 API函数 Private Declare Function sndPlaySoundFromMemory Lib "winmm.dll" Alias "sndPlaySoundA" (lpszSoundName As Any, ByVal uFlags As Long) As Long 音乐效果请“单击” Pr…...

web:[HCTF 2018]WarmUp
题目 点进页面,页面只有一张滑稽脸,没有其他的提示信息 查看网页源代码,发现source.php,尝试访问一下 跳转至该页面,页面显示为一段php代码,需要进行代码审计 <?phphighlight_file(__FILE__);class emm…...

程序开发常用在线工具汇总
菜鸟工具# https://c.runoob.com/ 编码# ASCII码# https://www.habaijian.com/ 在线转换# https://www.107000.com/T-Ascii/http://www.ab126.com/goju/1711.html Base64# 在线转换# https://www.qqxiuzi.cn/bianma/base64.htmhttp://www.mxcz.net/tools/Unicode.aspx …...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...