1 Linux 进程的睡眠和唤醒
在 Linux 中,仅等待 CPU 时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为 TASK_RUNNING。一旦一个运行中的进程时间片用完, Linux 内核的调度器会剥夺这个进程对 CPU 的控制权,并且从运行队列中选择一个合适的进程投入运行。
当然,一个进程也可以主动释放 CPU 的控制权。函数 schedule() 是一个调度函数,它可以被一个进程主动调用,从而调度其它进程占用 CPU。一旦这个主动放弃 CPU 的进程被重新调度占用 CPU,那么它将从上次停止执行的位置开始执行,也就是说它将从调用 schedule() 的下一行代码处开始执行。
Linux 中的进程睡眠状态有两种:一种是可中断的睡眠状态,其状态标志位
TASK_INTERRUPTIBLE;
另一种是不可中断 的睡眠状态,其状态标志位为 TASK_UNINTERRUPTIBLE。可中断的睡眠状态的进程会睡眠直到某个条件变为真,比如说产生一个硬件中断、释放 进程正在等待的系统资源或是传递一个信号都可以是唤醒进程的条件。不可中断睡眠状态与可中断睡眠状态类似,但是它有一个例外,那就是把信号传递到这种睡眠 状态的进程不能改变它的状态,也就是说它不响应信号的唤醒。不可中断睡眠状态一般较少用到,但在一些特定情况下这种状态还是很有用的,比如说:进程必须等 待,不能被中断,直到某个特定的事件发生。
在现代的 Linux 操作系统中,进程一般都是用调用 schedule() 的方法进入睡眠状态的,下面的代码演示了如何让正在运行的进程进入睡眠状态。
sleeping_task = current;
set_current_state(TASK_INTERRUPTIBLE);
schedule();
func1();
/* Rest of the code ... */
在第一个语句中,程序存储了一份进程结构指针 sleeping_task,current 是一个宏,它指向正在执行的进程结构。set_current_state() 将该进程的状态从执行状态 TASK_RUNNING 变成睡眠状态TASK_INTERRUPTIBLE。 如果 schedule() 是被一个状态为TASK_RUNNING 的进程调度,那么 schedule() 将调度另外一个进程占用 CPU;如果 schedule() 是被一个状态为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 的进程调度,那么还有一个附加的步骤将被执行:当前执行的进程在另外一个进程被调度之前会被从运行队列中移出,这将导致正在运行的那个进程进入睡眠,因为 它已经不在运行队列中了。
我们可以使用下面的这个函数将刚才那个进入睡眠的进程唤醒。
wake_up_process(sleeping_task);
在调用了 wake_up_process() 以后,这个睡眠进程的状态会被设置为 TASK_RUNNING,而且调度器会把它加入到运行队列中去。当然,这个进程只有在下次被调度器调度到的时候才能真正地投入运行。
2 无效唤醒
几乎在所有的情况下,进程都会在检查了某些条件之后,发现条件不满足才进入睡眠。可是有的时候进程却会在 判定条件为真后开始睡眠,如果这样的话进程就会无限期地休眠下去,这就是所谓的无效唤醒问题。在操作系统中,当多个进程都企图对共享数据进行某种处理,而 最后的结果又取决于进程运行的顺序时,就会发生竞争条件,这是操作系统中一个典型的问题,无效唤醒恰恰就是由于竞争条件导致的。
设想有两个进程 A 和 B,A 进程正在处理一个链表,它需要检查这个链表是否为空,如果不空就对链表里面的数据进行一些操作,同时 B 进程也在往这个链表添加节点。当这个链表是空的时候,由于无数据可操作,这时 A 进程就进入睡眠,当 B 进程向链表里面添加了节点之后它就唤醒 A 进程,其代码如下:
A 进程:
1 spin_lock(&list_lock);
2if(list_empty(&list_head)) {
3 spin_unlock(&list_lock);
4 set_current_state(TASK_INTERRUPTIBLE);
5 schedule();
6 spin_lock(&list_lock);
7 }
8
9/* Rest of the code ... */
10 spin_unlock(&list_lock);
B 进程:
100 spin_lock(&list_lock);
101 list_add_tail(&list_head, new_node);
102 spin_unlock(&list_lock);
103 wake_up_process(processa_task);
这里会出现一个问题,假如当 A 进程执行到第 3 行后第 4 行前的时候,B 进程被另外一个处理器调度投入运行。在这个时间片内,B 进程执行完了它所有的指令,因此它试图唤醒 A 进程,而此时的 A 进程还没有进入睡眠,所以唤醒操作无效。在这之后,A 进程继续执行,它会错误地认为这个时候链表仍然是空的,于是将自己的状态设置为 TASK_INTERRUPTIBLE 然后调用 schedule() 进入睡 眠。由于错过了 B 进程唤醒,它将会无限期的睡眠下去,这就是无效唤醒问题,因为即使链表中有数据需要处理,A 进程也还是睡眠了。
3 避免无效唤醒
如何避免无效唤醒问题呢?我们发现无效唤醒主要发生在检查条件之后和进程状态被设置为睡眠状态之前, 本来 B 进程的 wake_up_process() 提供了一次将 A 进程状态置为 TASK_RUNNING 的机会,可惜这个时候 A 进程的状态仍然是 TASK_RUNNING,所以 wake_up_process() 将 A 进程状态从睡眠状态转变为运行状态的努力 没有起到预期的作用。要解决这个问题,必须使用一种保障机制使得判断链表为空和设置进程状态为睡眠状态成为一个不可分割的步骤才行,也就是必须消除竞争条 件产生的根源,这样在这之后出现的 wake_up_process () 就可以起到唤醒状态是睡眠状态的进程的作用了。找到了原因后,重新设计一下 A 进程的代码结构,就可以避免上面例子中的无效唤醒问题了。
A 进程:
1 set_current_state(TASK_INTERRUPTIBLE);
2 spin_lock(&list_lock);
3if(list_empty(&list_head)) {
4 spin_unlock(&list_lock);
5 schedule();
6 spin_lock(&list_lock);
7 }
8 set_current_state(TASK_RUNNING);
9
10/* Rest of the code ... */
11 spin_unlock(&list_lock);
可以看到,这段代码在测试条件之前就将当前执行进程状态转设置成 TASK_INTERRUPTIBLE 了,并且在链表不为空的情况下又将自己置为 TASK_RUNNING 状态。这样一来如果 B 进程在 A 进程进程检查了链表为空以后调用 wake_up_process(),那么 A 进程的状态就会自动由原来 TASK_INTERRUPTIBLE变成 TASK_RUNNING,此后即使进程又调用了 schedule(),由于它现在的状态是 TASK_RUNNING,所以仍然不会被从运行队列中移出,因而不会错误的进入睡眠,当然也就避免了无效唤醒问题。
4 Linux 内核的例子
在 Linux 操作系统中,内核的稳定性至关重要,为了避免在 Linux 操作系统内核中出现无效唤醒问题,Linux 内核在需要进程睡眠的时候应该使用类似如下的操作:
/* ‘q’是我们希望睡眠的等待队列 */
DECLARE_WAITQUEUE(wait,current);
add_wait_queue(q, &wait);
set_current_state(TASK_INTERRUPTIBLE);
/* 或 TASK_INTERRUPTIBLE */
while(!condition) /* ‘condition’ 是等待的条件 */
schedule();
set_current_state(TASK_RUNNING);
remove_wait_queue(q, &wait);
上面的操作,使得进程通过下面的一系列步骤安全地将自己加入到一个等待队列中进行睡眠:首先调用 DECLARE_WAITQUEUE () 创建一个等待队列的项,然后调用 add_wait_queue() 把自己加入到等待队列中,并且将进程的状态设置为TASK_INTERRUPTIBLE 或者 TASK_INTERRUPTIBLE。然后循环检查条件是否为真:如果是的话就没有必要睡眠,如果条件不为真,就调用 schedule()。当进程 检查的条件满足后,进程又将自己设置为 TASK_RUNNING 并调用 remove_wait_queue() 将自己移出等待队列。
从上面可以看到,Linux 的内核代码维护者也是在进程检查条件之前就设置进程的状态为睡眠状态,然后才循环检查条件。如果在进程开始睡眠之前条件就已经达成了,那么循环会退出并用 set_current_state() 将自己的状态设置为就绪,这样同样保证了进程不会存在错误的进入睡眠的倾向,当然也就不会导致出现无效唤醒问题。
下面让我们用 linux 内核中的实例来看看 Linux 内核是如何避免无效睡眠的,这段代码出自 Linux2.6 的内核 (linux-2.6.11/kernel/sched.c: 4254):
4253/* Wait for kthread_stop */
4254 set_current_state(TASK_INTERRUPTIBLE);
4255while (!kthread_should_stop()) {
4256 schedule();
4257 set_current_state(TASK_INTERRUPTIBLE);
4258 }
4259 __set_current_state(TASK_RUNNING);
4260return0;
上面的这些代码属于迁移服务线程 migration_thread,这个线程不断地检查 kthread_should_stop(),
直 到 kthread_should_stop() 返回 1 它才可以退出循环,也就是说只要 kthread_should_stop() 返回 0 该进程就会一直睡 眠。从代码中我们可以看出,检查 kthread_should_stop() 确实是在进程的状态被置为 TASK_INTERRUPTIBLE 后才开始执行 的。因此,如果在条件检查之后但是在 schedule() 之前有其他进程试图唤醒它,那么该进程的唤醒操作不会失效。
小结
通过上面的讨论,可以发现在 Linux 中避免进程的无效唤醒的关键是在进程检查条件之前就将进程的状态置为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE,并且如果检查的条件满足的话就应该将其状态重新设置为 TASK_RUNNING。这样无论进程等待的条件是否满足, 进程都不会因为被移出就绪队列而错误地进入睡眠状态,从而避免了无效唤醒问题。
原文标题:关于 Linux 进程的睡眠和唤醒 ,来看这篇就够了~
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
收藏
人收藏
扫一扫,分享给好友
复制链接分享
评论
发布评论请先 登录
相关推荐
Linux开源社区宣布未来会把内核C语言版本升级到C11
还在使用89年版C语言的Linux内核,现在终于要做出改变了。
Linux爱好者 发表于 03-24 14:59 •
237次
阅读
卸载DevEco Device Tool的方法
需要安装脚本即,无论是Windows Linux平台,开发者在执行安装时,都会先行进行卸载操作,开发者单独执行卸载操作。
对于有特殊...
发表于 03-24 10:06 •
248次
阅读
ARM-Linux应用开发和单片机开发的不同
针对ARM-Linux程序的开发,主要分为三类:应用程序开发、驱动程序开发、系统内核开发。针对不同种....
嵌入式ARM 发表于 03-23 16:01 •
278次
阅读
RT-Thread能否终止正执行的命令?是怎么操作的?
我一直有个问题,像这种一直运行任务,没有什么命令可以终止任务
我看RT-Thread, msh其本是操作很多命令基本与Linux的shell...
发表于 03-23 14:17 •
250次
阅读
鸿蒙内核实现用户态快速互斥锁Futex设计资料合集
Futex(Fast userspace mutex,用户态快速互斥锁),系列篇简称 快锁 ,是一个在 Linux 上实现锁定和构建高级抽象锁如信号量和...
发表于 03-23 14:12 •
126次
阅读
怎样在Ubuntu系统上下载和安装一些软件呢
Select lightdm
在这个逐步的教程中学习如何在 Ubuntu 上安装 Budgie 桌面。在所有各种 Ubuntu 版本中,Ubuntu Budgie...
发表于 03-22 11:42 •
525次
阅读
昉星光开发板介绍
昉·星光基于RISC-V架构,搭载JH7100视觉处理芯片,使开源达到更高的水平,开发者从而得到更大的自由及更强的能力去创新和...
发表于 03-21 17:46 •
1745次
阅读
昉惊鸿7100芯片 datasheet
发表于 03-21 17:43 •
1694次
阅读
昉星光单板计算机产品手册
发表于 03-21 17:36 •
1269次
阅读
盘点 5 款值得收藏的 Linux 开发板
[quote]导读:我给大家整理了 5 款值得收藏的 Linux 开发板,有些已经涨价很多了。至于未来还能不能继续“理财”,大家自行判断...
发表于 03-21 15:54 •
962次
阅读
SONiC正在成为云架构中领先的开放网络操作系统
SONIC是一个基于 Linux 的开源网络操作系统,能够在多个厂商的交换机和专用集成电路(ASIC....
是德科技KEYSIGHT 发表于 03-20 13:16 •
271次
阅读
ADI为Linux发行版扩充器件驱动 Ti Group选择是德科技5G测试平台
Keysight Technologies, Inc.(纽约证券交易所代码:KEYS)是一家领先的技....
发表于 03-18 14:51 •
1284次
阅读
关于Linux命令行烧写固件
说明:如果已经安装旧版本的烧写工具,请先点击“驱动卸载”按钮下载驱动,然后再点击 “驱动安装”按钮安....
HarmonyOS官方合作社区 发表于 03-18 10:57 •
172次
阅读
i.MX6ULL驱动开发3—GPIO寄存器配置原理
介绍了字符设备驱动的两种新旧开发方式,并使用一个虚拟的字符驱动来学习字符设备的开发的流程。
码农爱学习 发表于 03-18 08:17 •
230次
阅读
Hi3861开发板串口看不到命令行是为什么?
1)开发板启动,只有教程中的AT命令能使用,敲回车报ERROR;
2)开发板没有linux常用命令吗(ls、pwd、cd)?还...
发表于 03-17 15:41 •
967次
阅读
i.MX6ULL嵌入式Linux开发6-系统烧写到eMMC
本篇主要介绍了Linux移植的系统打包烧录的EMMC的方法,使用MfgTool工具,将**uboot....
码农爱学习 发表于 03-17 09:17 •
1785次
阅读
一文深入理解操作系统的进程调度
想深入理解操作系统的进程调度,需要先获得一些准备知识,这样后面就不懵圈啦:
Linux爱好者 发表于 03-16 10:58 •
196次
阅读
如何编译已有的C++去生成可在OpenHarmony系统使用的动态库文件呢
原有Linux环境下的C++ 项目,可通过在CMakeList文件中设置使用的工具链,编译出各平台开发板上可使用的so文件。
请问,我现...
发表于 03-16 10:42 •
847次
阅读
如何鉴别Linux服务器是否被入侵
随着开源产品的越来越盛行,作为一个Linux运维工程师,能够清晰地鉴别异常机器是否已经被入侵了显得至....
马哥Linux运维 发表于 03-16 10:08 •
254次
阅读
Linux namespace和cgroups简介
先放结论,namespace 是用来做资源隔离, cgroup 是用来做资源限制。
马哥Linux运维 发表于 03-16 09:03 •
246次
阅读
基础的Linux操作命令
IC设计是在linux环境下,很多操作需要在Terminal中进行,因此想要学习IC设计,就必须先需....
FPGA之家 发表于 03-15 13:42 •
174次
阅读
BPF系统调用与Tracing类型的BPF程序
既然是提供向内核注入代码的技术,那么安全问题肯定是重中之重。平时防范他人通过漏洞向内核中注入代码,这....
Linux阅码场 发表于 03-14 16:42 •
371次
阅读
盘点5款值得收藏的Linux开发板
最近股市又哀鸿遍野……于是,那句 “树莓派是最好的理财产品”又开始在我耳边萦绕。“缺芯”笼罩之下,开....
话说科技 发表于 03-14 15:12 •
375次
阅读
霍尼韦尔智能楼宇技术助力小镇建设_ADI为Linux发行版扩充1000多个器件驱动
2022年2月11日,中国北京——霍尼韦尔宣布与中建二局安装工程有限公司和北京市设备安装工程集团有....
电子魔法师 发表于 03-14 15:04 •
660次
阅读
i.MX6ULL嵌入式Linux开发5-根文件系统完善
上篇文章,使用BusyBox构建了基础的嵌入式Linux系统的根文件系统,基本的功能可以正常运行,但....
码农爱学习 发表于 03-14 08:44 •
203次
阅读
i.MX6ULL嵌入式Linux开发4-根文件系统构建
本篇使用BusyBox来构建根文件系统,并通过NFS网络调试的方式实现根文件系统挂载测试,实测时解决....
码农爱学习 发表于 03-14 08:41 •
220次
阅读
i.MX6ULL嵌入式Linux开发3-Kernel移植
本文进行Linux内核的移植。
码农爱学习 发表于 03-14 08:36 •
230次
阅读
提高Linux服务器性能的20个技巧
Linux功能丰富、强大、灵活,你可以用它完成各种任务,在这篇文章中,我们将讨论一些提高Linux....
Linux爱好者 发表于 03-11 10:14 •
215次
阅读
51点灯与Linux驱动点灯的区别
嵌入式初学者入门的第一个“项目”就是LED点灯,那么,本文带你看看51、STM32、Linux点灯有....
strongerHuang 发表于 03-10 17:37 •
1460次
阅读
为VisionFive星光板上创建Debian系统镜像
在RISC-V 星光板VisionFive上创建Debian/Linux系统
发表于 03-09 20:52 •
14次
阅读
Linux内核代码修改将为性能测试获8450%提升
Jason Donenfeld 是 WireGuard 的主要开发者,同时他也是 Linux 内核随....
马哥Linux运维 发表于 03-09 14:16 •
252次
阅读
Linux私房菜基础篇-第三版
Linux私房菜基础篇-第三版免费下载。
发表于 03-07 16:46 •
51次
阅读
《Linux命令行大全》(英文版)
[美]William E. Shotts Jr. 著
发表于 03-07 16:29 •
23次
阅读
i.MX6ULL嵌入式Linux开发2-uboot移植实践
上篇文章,我们介绍了如何使用NXP原厂的uboot进行编译和烧写,将uboot运行在自己的开发板上。....
码农爱学习 发表于 03-07 09:00 •
1734次
阅读
i.MX6ULL嵌入式Linux开发1-uboot移植初探
本系列教程以i.MX6ULL处理器的ARM开发板为实验基础,学习记录嵌入式Linux开发的各种知识与....
码农爱学习 发表于 03-07 08:57 •
1277次
阅读
万象奥科RZ G2L核心板高低温测试
评估测试RZ/G2L核心板环境适应性,测试低温启动、高温工作、高低温循环状态下的工作情况。
武汉万象奥科 发表于 03-03 15:27 •
10次
阅读
RZ G2L核心板以太网接口性能测试
武汉万象奥科RZ/G2L核心板支持2路千兆以太网接口,评估测试RZ/G2L双网口实际传输速率。
武汉万象奥科 发表于 03-03 14:04 •
10次
阅读
RZ/G2L核心板功耗测试
测试RZ/G2L核心板静态功耗与CPU满负载时的负载功耗,辅助产品设计中的散热方案评估。
武汉万象奥科 发表于 03-03 11:35 •
9次
阅读
linux常用命令大全
linux常用命令大全,一些常用的命令都可以找到
发表于 03-03 09:20 •
50次
阅读
为什么要在汽车上使用SOA架构
整车E/E架构升级,硬件架构上从分布式ECU向域集中式,进一步向中央集中式+区域控制器升级;
发表于 03-02 09:15 •
105次
阅读
瑞萨电子推出64位RISC-V CPU内核RZ/Five通用MPU,开创RISC-V技术先河
瑞萨电子今日宣布,推出基于64位RISC-V CPU内核的RZ/Five通用微处理器(MPU)——R....
发表于 03-01 13:54 •
628次
阅读
keil5+STM32F103C8T6设计的智能插座+人体感应灯 支持跨平台编译运行
这是基于STM32设计的智能插座+人体感应灯。SRM32F103C8T6最小系统板、红外热释电人体感....
DS小龙哥-嵌入式技术 发表于 02-28 13:19 •
2040次
阅读
迅为iTOP-STM32MP157开发板
迅为ITOP-STM32MP157是基于ST的STM32MP157芯片开发的一款开发平台。在STM3....
发表于 02-23 14:43 •
18次
阅读
迅为STM32MP157开发板入门教程之外设功能验证
迅为STM32MP157开发板入门教程之外设功能验证
平常心0 发表于 02-23 14:16 •
27次
阅读
国产平台T3如何快速部署电力能源神器-Docker容器
前 言随着电网数据信息化的深入,电网的各种应用服务增长迅速,原有应用服务部署方式面临着资源利用率低、....
Tronlong创龙科技 发表于 02-17 13:56 •
24次
阅读
Petalinux2020.01 内核DMA驱动调试说明
Petalinux2020.01 内核DMA驱动调试过程分享
赛灵思 发表于 02-16 16:21 •
165次
阅读
为什么要用C语言实现面向对象
不知道有多少人去了解过语言的发展史,早期C语言的语法功能其实比较简单。随着应用需求和场景的变化,....
硬件攻城狮 发表于 02-16 16:19 •
431次
阅读
Linux编程入门
Linux编程入门
发表于 02-16 14:55 •
64次
阅读
如何配置和操作Linux驱动程序开发板
本文档概述了利用Linux开发板为 Linux 内核开发驱动程序的基础知识,并简单介绍了如何配置和操....
德州仪器 发表于 02-15 13:36 •
392次
阅读
选型必备!最新最全的工业核心板目录大全来了!!
随着嵌入式技术的发展,越来越多的处理器应运而生。除了国外的主流半导体厂家,国内的半导体厂家也异军突起....
Tronlong创龙科技 发表于 02-15 09:30 •
25次
阅读
一文详细了解五种IO模型
五种IO模型包括:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO。
硬件攻城狮 发表于 02-14 14:38 •
551次
阅读
Linux总线、设备、驱动模型的探究
Linux总线、设备、驱动模型的探究
发表于 02-14 12:01 •
65次
阅读
Cortex-M可以跑Linux操作系统吗?
ARM处理器的体系结构定义了指令集(ISA)和基于这一体系结构下处理器的模型。ARM的指令集从ARM....
发表于 02-08 15:55 •
42次
阅读
51、STM32、Linux点灯有什么区别?
嵌入式初学者入门的第一个“项目”就是LED点灯,那么,本文带你看看51、STM32、Linux点灯有....
发表于 02-08 15:32 •
101次
阅读
U-Boot架构浅析
导读:嵌入式Linux系统搭建,bootloader是必不可少的一环,而U-Boot已成嵌入式Lin....
发表于 02-07 11:56 •
54次
阅读
深度解析U-Boot网络实现
对于U-Boot而言,并没有完整的实现上述模型,u-boot需要控制固件的尺寸,所以根据需要做了一些....
发表于 02-07 11:53 •
47次
阅读
阅读内核系列之内核调度器为何全局导出
[导读] Linux内核代码庞大,阅读内核书籍总觉得云山雾绕,纸上得来终觉浅,希望通过阅读代码撰写笔....
发表于 02-07 11:49 •
42次
阅读
关于MMU那些事儿
如果我们一直是单任务处理,则不会有任何问题,也或者应用程序所需的内存总是非常小,则这种架构是不会有任....
发表于 02-07 11:27 •
40次
阅读
如何使用v4l2 API读取摄像头
V4L2是Video For Linux的第二个版本,它是Linux的视频捕获的API。在这里,您可....
发表于 02-07 11:16 •
30次
阅读
上古神器vim系列之初探
[导读] 一直以来VIM也用一些,但是用的不熟,最近看了些文章,发现这个东西有必要发大力气系统的练习....
发表于 02-07 11:13 •
52次
阅读
AM4379 AM437x ARM Cortex-A9 微处理器 (MPU)
TI AM437x高性能处理器基于ARM Cortex-A9内核。
这些处理器通过3D图形加速得到增强,可实现丰富的图形用户界面,还配备了协处理器,用于进行确定性实时处理(包括EtherCAT,PROFIBUS,EnDat等工业通信协议)。该器件支持高级操作系统(HLOS)。基于Linux的® 可从TI免费获取。其它HLOS可从TI的设计网络和生态系统合作伙伴处获取。
这些器件支持对采用较低性能ARM内核的系统升级,并提供更新外设,包括QSPI-NOR和LPDDR2等存储器选项。
这些处理器包含功能方框图中显示的子系统,并且后跟相应的“说明”中添加了更多信息说明。
处理器子系统基于ARM Cortex-A9内核,PowerVR SGX™图形加速器子系统提供3D图形加速功能以支持显示和高级用户界面。
可编程实时单元子系统和工业通信子系统(PRU-ICSS与ARM内核分离,允许单独操作和计时,以实现更高的效率和灵活性.PRU-ICSS支持更多外设接口和EtherCAT,PROFINET,EtherNet /IP,PROFIBUS,以太网Powerlink,Sercos,EnDat等...
发表于 09-25 11:51 •
536次
阅读
|