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 …...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
