1. Kernel
1.1 编译安装5.9.6 on Ubuntu
https://phoenixnap.com/kb/build-linux-kernel 需要的改动:
1.UEFI BIOS, secure boot要禁止,不然报linux kernel: invalid signature, you need to load the kernel first’,可也自己生成signature:https://help.univention.com/t/problem-booting-server-shows-invalid-signature-you-need-to-load-the-kernel-first/13557
2.改.config两行,去掉找不到的debian license,去debug以减小尺寸,改后为:
CONFIG_SYSTEM_TRUSTED_KEYS=""
CONFIG_DEBUG_INFO_BTF=n
3.生成的initrd.img需要strip剪裁,不然启动时装入kernel后停在initrd.img处,办法是https://unix.stackexchange.com/questions/270390/how-to-reduce-the-size-of-the-initrd-when-compiling-your-kernel :
cd /lib/modules/
find . -name *.ko -exec strip --strip-unneeded {} +
cd
sudo make install
1.2 编译安装5.13.1 on Ubuntu
sudo apt update && sudo apt upgrade
sudo reboot
sudo apt install build-essential libncurses5-dev fakeroot xz-utils libelf-dev flex bison libssl-dev dwarves zstd
cd ~/Downloads
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.13.1.tar.xz
tar xf linux-5.13.1.tar.xz
cd linux-5.13
sudo find /boot/ ( -iname "*config*" -a -iname "*`uname -r`*" ) -exec cp -i -t ./ {} ;
mv *`uname -r`* .config
ls /boot | grep config
sudo gedit .config &
==comment out: #CONFIG_SYSTEM_TRUSTED_KEYS, #CONFIG_SYSTEM_REVOCATION_KEYS,
==enable: CONFIG_PCIEPORTBUS=y, CONFIG_PCIEAER=y, CONFIG_PCIEAER_INJECT=y, CONFIG_PCIE_ECRC=y,
sudo make menuconfig
sudo make -j 8
sudo make bzImage -j 8
sudo make modules_install
sudo make install
cd /lib/modules/5.13.1
sudo find . -name *.ko -exec strip --strip-unneeded {} +
sudo update-initramfs -c -k 5.13.1
sudo update-grub
sudo reboot
uname -mrs
1.3 remove kernel
https://phoenixnap.com/kb/ubuntu-remove-old-kernels
2 .Zhphyr RTOS
Zhphyr文档:https://docs.zephyrproject.org/2.7.0/zephyr.pdf? https://docs.zephyrproject.org/latest/
Zephyr on ESP32: https://www.zephyrproject.org/zephyr-rtos-on-esp32/
2.0 west:https://docs.zephyrproject.org/latest/boards/xtensa/esp32/doc/index.html 需要运行west init才能运行
west espressif update
2.1 install Zephyr
https://docs.zephyrproject.org/latest/getting_started/index.html?upgrade stc: https://askubuntu.com/questions/1090223/how-to-upgrade-dtc-version-in-ubuntu-18-04
https://docs.zephyrproject.org/latest/getting_started/index.html 装Zephyr,编译前要加装 pip3 install pyelftools 不然出错:
cd ~/zephyrproject/zephyr
west build -p auto -b arduino_nano samples/basic/blinky
起步还可以读:https://www.zephyrproject.org/blinking-an-led-with-zephyr-rtos-creating-an-application/
2.2 install ESP32 tool chain
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-macos-setup.html#get-started-prerequisites?
在Connect your device一节,Linux下可用dmesh | grep /dev/ttyUSB*确定插入的串口号。
装ESPRESSIF: https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/get-started/index.html#get-started-get-prerequisites 在configure之前要加装:
sudo apt install python-pip;pip install click
另一错误多半是缺省用了Python2,去掉改用Python3的办法是:
sudo apt purge -y python2.7-minimal
sudo ln -s /usr/bin/python3 /usr/bin/python
sudo apt install -y python3-pip
sudo ln -s /usr/bin/pip3 /usr/bin/pip
python --version
错误在menuconfig失败,找不到不到支持bidict>=0.21.0的模块,最后将Ubuntu升级到20.04按此文装成功:https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-macos-setup.html#get-started-prerequisites 编译用idf.py
2.3 不匹配
至此,Zephyr和ESP32各自的工具都就绪,可是Zephyr编译ESP32不是用ESP32的工具链,因此我按https://www.zephyrproject.org/zephyr-rtos-on-esp32/ 装后,编译时才发现Zephyr version: 3.0.99是,cmake需3.20,west需0.7.1,dtc需1.5.0,似乎是官网要求的:https://docs.zephyrproject.org/latest/getting_started/index.html
换句话无论按哪个指南做,其中部分软件更新后,都可导致失败。按官网发现sdk是0.14而非0.12,又是个耗时的过程。最后编译错误出在
1)-- Found west (found suitable version "0.12.0", minimum required is "0.7.1")
2)-- Found dtc: /usr/bin/dtc (found suitable version "1.5.0", minimum required is "1.4.6")
3)CMake Error at /home/jodai/zephyrproject/zephyr/cmake/toolchain/espressif/generic.cmake:52 (message):
Unable to find toolchain in
/home/jodai/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/xtensa-esp32-elf
错误3最重要,看Cmake可知它产生的工具链是: ~/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/xtensa-esp32-elf而实际的是:~/.espressif/tools/zephyr/xtensa-esp32-elf/xtensa-esp32-elf,换句话工具链要由github下指定版本。
2.3 nRF52840-MDK
支持 Bluetooth 5、Bluetooth Mesh、Thread、IEEE 802.15.4、ANT 等无线协议,带有片上 Arm CryptoCell 加密子系统,集成了多功能调试器 Arm Mbed DAPLink,提供“拖放式”下载固件、USB CDC 虚拟串口、CMSIS-DAP 仿真等功能。板载Microchip 两端口 USB 2.0 高速集线器,只需一个 USB 接口即可同时使用 DAPLink 和 nRF52840 的 USB 设备控制器;有64Mbit 超低功耗 QSPI FLASH、可编程按键、RGB LED、晶片天线及外部天线连接器等硬件资源。
nRF52840-MDK 支持Zephyr框架,跨平台开发,可以玩转多种开源软件框架(如:nRF5 SDK, Mesh, OpenThread, ZigBee 3.0, Mbed OS 5, Zephyr, Mynewt, iBeacon, Eddystone, Web Bluetooth 等等),提供丰富的在线文档及示例,帮助加速物联网应用开发。https://bbs.huaweicloud.com/blogs/163505
中肯提醒:如果您是 MCU 世界的新手且功耗不是大问题,请选择 ESP32。 Nordic 的东西(比如这个板子)更适合实际的生产绑定产品开发和原型设计,更加健壮和灵活,但根据我的经验,IDE 并不直观,支持库也不那么广泛。 我只向有经验的用户或快速学习者推荐这个产品。 如果您知道自己在做什么,这是一个很棒的产品,并且比官方的 Nordic nRF 开发板小得多,后者是无用的巨大。直观的 arduino IDE 更好地支持相同外形尺寸的 esp32 板,并具有广泛的支持库、示例代码示例和庞大的社区来提供帮助。
Zephyr用了类似Linux的menuconfig,Intel曾在开始时很热心,现在是NXP。缺点是比较复杂,样本多是板子厂家提供通用性一般,感觉工具出问题比较麻烦不如make直接给出原因。arduino IDE就太简洁直观了。所用版本备忘:
$ cmake --version
cmake version 3.23.0
$ python3 --version
Python 3.8.10
$ dtc --version
Version: DTC 1.5.0
$ west --version
West version: v0.12.0
~/zephyrproject/zephyr$ west build -p auto -b esp32 samples/basic/blinky
https://github.com/espressif/crosstool-NG/releases
3. FreeRTOS
https://github.com/freertos? https://zhuanlan.zhihu.com/p/145699420 https://blog.csdn.net/Chuangke_Andy/article/details/115397230 官网的18f452代码不能编译:1)所有源码是同一目录上载到官网变成多级目录 2)XIDE加include路径无效 3)核心已经修改不配套 4)原编译器用PICC与XC8不兼容,XC8声称的#asm也不能用,asm()可用。新片PIC18F26/46/56Q43为28/40/44/48pin,PIC18F26Q43-I/SP $2.08,PIC18F45Q43T-I/PT $1.7, PIC18F4550T-I/PT $7.91:
18F4550T-I/PT TQFP44,10x10 2.0-5.5V 32K 2KB-DRAM 256B-EEPROM 48MHz I/O-35 UART/I2C/SPI 10bx13chAD no-DAC USB2.0
18F45Q43T-I/PT TQFP48,7x7 1.8-5.5V 32K 2KB-DRAM 1KB-EEPROM 64MHz I/O-36 +I2S+PWM 12bx35chAD 8bx1ch no-USB
18F45K50-I/P DIP40 2.3-5.5V $2.08 32K 2KB-DRAM 256B-EEPROM 48MHz I/O-35 UART/I2C/SPI 10bx13chAD 5bx1ch USB2.0
https://github.com/tahaemara/MicroRTOS-for-PIC-18F-Family PIC18F4550用mikroc PRO for PIC编译。https://www.youtube.com/watch?v=hYynOanPZWU 使用T0时钟延迟和软件堆栈。uP初始化栈指针STKPTR为0,TOSU:TOSH:TOSL存有当前栈指针STKPTR的内容24bit,操作栈时需禁止GIE。STKOVF/STKUNF为上溢/下溢标志。上溢时若STVREN置位将中断,否则栈顶0x7F内容被重写;但是人工设置STVREN不会中断。STKUNF同理。新品F45K50:1) Have lesser flash write cycles
2) Needs no crystal for USB. built in oscillator works up to 16 MHz.
3) Oscillator configuration, config bits are slightly different.
4) Has a 5-bit DAC and CTMU peripheral for capacitive sensing.
5) might not supported by free mcc18 compilers
6) not supported by PICkit2 (non-official device file supports this)
7) LPT1OSC config bit not available on the K part.
https://github.com/FreeRTOS/FreeRTOS/releases
以前就说过,Pic因为时间长芯片程序会丢,现有用Pic的产品需重编程,否则不要用。ESP32价廉物美可覆盖多数嵌入式,但是基于ARM的板子也很多。我用Arduino IDE试了ESP32版本的FreeRTOS-因为CPU是双核,很好用很容易,如果sample code夹杂了其它CPU的.h文件需要改造。
3.1 FreeRTOS的学习笔记
https://freertos.org/Documentation/161204_Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf
FreeRTOS
└─Source
├─tasks.c FreeRTOS source file - always required
├─list.c FreeRTOS source file - always required
├─queue.c FreeRTOS source file - nearly always required
├─timers.c FreeRTOS source file - optional
├─event_groups.c FreeRTOS source file - optional
└─Portable
├─[compiler]/[architecture] All C/asm files
└─MemMang
└─heap_?.c heap_?.c, ?=1-5, heap files
目前v10,由v9开始无需heap manager,有5个heap?.c可选:1适用于任务创建后无需删除,没有回收的环境;3类似传统malloc/free但有线程安全性且忽略TOTAL_HEAP_SIZE;4是2的增强,会把释放的相连的缓冲池合并;5用于缓冲池不连续的环境需要配置。函数是pvPortMalloc() 和vPortFree(),也有统计函数xPortGetFreeHeapSize()(unit in bytes)和configTOTAL_HEAP_SIZE定义堆的大小单位是栈宽,如64bit则为64bit。FreeRTOS中,函数前缀v返回void,x返回变量,pv返回void指针,pd是常量。注意ESP32的RAM类型多,官网另有heap说明。注意在原版Vanilla FreeRTOS中uxTaskGetStackHighWaterMark返回Dword长度而在ESP-IDF中返回Byte长度,是堆栈空余最小值。https://www.youtube.com/watch?v=Qske3yZRW5I#t=10m38s?
配置文件是FreeRTOS/Source/include/FreeRTOSConfig.h,缺省配置的tick是10ms(可改为1ms)是所有延迟的最小单位,延迟时间用pdMS_TO_TICKS()转为tick,延迟函数是vTaskDelay()和vTaskDelayUntil(),后者是定周期的任务执行。如果不用系统延迟函数,较低优先级的任务将被堵塞。FreeRTOS配置configUSE_PREEMPTION和configUSE_TIME_SLICING可为抢占式或合作(轮转)式,通常两者都置1。任务是一个死循环的函数或进程,其堆栈和变量均在其heap中,由主程序中的vTaskStartScheduler系统调度将其在条件满足时投入运行,这些条件包括端口或定时中断、各种信号量等。任务相关函数有:xTaskCreate、xTaskCreateStatic、vTaskPrioritySet、uxTaskPriorityGet、vTaskDelete()。任务状态有:running、ready、blocked、suspended四种状态。任务优先级通常是0-31。最低为0是系统idle任务专用,idle任务实现一些系统统计功能,可用vApplicationIdleHook添加用户任务,如将系统置于低功耗,此时configUSE_IDLE_HOOK需为1。
Queue队列是一个FIFO先进先出的数据区,但数据的拷贝也可以取消或重写,任何任务均可访问。通常用于任务间通信,也可用于任务与中断服务程序的通信。在任务等待队列数据时会进入blocked态。可能会有多个任务因等待数据而进入blocked;一旦数据到来,只有最高优先级或等待最久的任务会被激活。 同样,可能会有多个写任务因队列满处于blocked,待可写入时只有一个被激活,但写入操作可指定最大等待时延。Queue由 xQueueCreateStatic() 或 xQueueCreateStatic()创建并返回handle;由xQueueSendToBack()和xQueueSendToFront()写入,或xQueueSendToFrontFromISR()和xQueueSendToBackFromISR();读是xQueueReceive()。Queue的内容可以是任何数据结构。
TCP TCP/IP stack包的例子:从网络接收的数据通过结构IPStackEvent_t的发送到TCP/IP任务,结构中的eEventType设置为eNetworkRxEvent,而pvData 指向包含接收数据的缓冲区;根据eEventType来处理各类事件/数据。
如果必须用多个Queue来处理数据,可以把它们放入Queue Set队列集中,而无需逐个判断是否有数据待处理,并可捆绑Semaphores信号灯。这是通过将configUSE_QUEUE_SETS置1和xQueueCreateSet、xQueueAddToSet来实现的。然后用xQueueSelectFromSet先取得有数据的Queue Handle或SemaphoreHandle_t handle,比较其名后用xQueueReceive或xSemaphoreTake做相应处理。
mail box在此是长度为1的Queue。Queue通常为另一个任务或中断服务程序提供数据,后者则取出数据;mailbox不同的是它的写xQueueOverwrite是覆盖性的;数据可为任何任务或中断服务程序通过xQueuePeek来读但并不取出,用户代码可用返回pdTRUE来表明新数据,否则pdFALSE。
软件计时器用于以固定频率周期或在未来设定时刻执行程序,籍由其ATimerCallback回调函数实现。这是可选的,需要configUSE_TIMERS置1并包括FreeRTOS/Source/timers.c。回调函数不能被blocked,因此不能调用API、vTaskDelay或xTicksToWait不为0的xQueueReceive。Software Timer由xTimerCreateStatic或xTimerCreate创建,相关函数有xTimerStart/Stop/Reset/ChangePeriod等。它可以是One shot或Auto-reload,有Dormant休眠和Running运行两种状态,Timer service或称RTOS daemon任务自动运行并由configTIMER_TASK_PRIORITY和configTIMER_TASK_STACK_DEPTH配置,API通过timer command queue给它下达命令,队列长度是configTIMER_QUEUE_LENGTH。每个Timer可通过vTimerSetTimerID()设置ID,由pvTimerGetTimerID()直接读取,不通过命令队列。xTimerChangePeriod可以改变定时,例如在CheckTasksAreRunningWithoutError返回错误后。
中断服务调用的API函数(*fromISR)导致更高优先级任务应当被换入时,FreeRTOS设计为在中断内不做自动切换,但可通过portYIELD_FROM_ISR()或portEND_SWITCHING_ISR()来切换,两者功能相同,都是taskYIELD()的可在中断内使用的的版本,有的只移植其一。调用的参数是pxHigherPriorityTaskWoken,它应初始化为pdFALSE,在发生上述情况下被RTOS置为pdTRUE。如果初始化为NULL则禁止其功能。极少数移植只允许portYIELD_FROM_ISR()在终端服务程序的最后使用。
Deferred Interrupt Processing是指尽可能的把中断服务的功能放在服务程序之外的任务中,以减小中断开销和使用更多的API功能,该任务的优先级通常高于其它任务,用xSemaphoreCreateBinary创建且调用xSemaphoreTake()使自己挂起。在中断服务程序中调用xSemaphoreGiveFromISR()给该任务、开中断后调用portYIELD_FROM_ISR(),将直接切换到该任务。这种semaphore的用法与常规不同-不给还,更像是长度为1的Queue。vPortGenerateSimulatedInterrupt()可用于中断仿真。
centralized deferred interrupt processing是指用xTimerPendFunctionCallFromISR()延迟中断处理给RTOS Daemon守护程序,避免每个中断都要一个外处理任务;该函数的中断不安全版是xTimerPendFunctionCall。许多API函数有中断安全版(fromISR),例如Queue读写函数。
嵌套的中断需要配置,注意Cortex-M的中断是数字越小优先级越高,可以用Assert使得FreeRTOS检测。
资源管理:任务在访问不可重入且非线程安全的共享资源时,需要独占访问权直至资源到一致状态。用于例如对非原子的读-修改-写代码用Mutex以免被打断,或在打印字符行时不会被插入其它字符串,两者均可用宏调用taskENTER_CRITICAL()和taskEXIT_CRITICAL()来包围,称为基本临界区。在中断中临界区可用uxSavedIntruptStatus = taskENTER_CRITICAL_FROM_ISR();和taskEXIT_CRITICAL_FROM_ISR(uxSavedIntrStatus);来包围。另一对可以使用的函数是vTaskSuspendAll和xTaskResumeAll。
binary semaphore通常用于事件同步或延迟的中断处理,counting semaphore通常用于事件或资源计数。常规的它是0~N之间的变量;但在FreeRTOS中是长度为N内容不论的Queue。它由configUSE_COUNTING_SEMAPHORES置1使能,用xSemaphoreCreateCounting创建,多次调用xSemaphoreGiveFromISR可以设置大于1的值。
mutual exclusion(Mutex)互斥锁与之类似,常用于任务中来独占被多个任务共享的资源。Mutex可理解为token,获得它的任务独享资源,用后必须交回;而binary semaphore多用于事件同步,获得后不必交回多被丢弃。由configUSE_MUTEXES使能,xSemaphoreCreateMutex创建,仍用xSemaphoreTake和xSemaphoreGive来获得和交回。低优先级的任务能因获得Mutex而导致高优先级的任务被blocked,直至Mutex被释放,这被称为Priority inversion优先级反转。由于低优先级的任务不被换出,相当于它具有了与高优先级任务同样的优先级,又称Mutex有Priority Inheritance优先级继承的特点;而binary semaphore没有这个特点。如果是同一优先级,刚获得Mutex的任务B要等下次调度;但前一任务A可能再次获得Mutex并继续运行,导致任务B不能即时运行,这需要适当的编码以平衡任务的运行时间,或及时调用taskYIELD。
如果两个任务互相用Mutex独占了对方所需资源,两者都因等待而无法运行,这被称为Dead lock死锁,需要在编码时避免或给Mutex等待限时,或者将资源置于Gatekeeper任务管理之下,需要的任务都通过它来获得资源。使用时configUSE_TICK_HOOK置1,然后定义vApplicationTickHook(),它在每个tick被执行,因此必须简短。
Dead Lock死锁也可在单个任务中因多次获取Mutex而忘了释放而产生,这时可用Recursive Mutex,同一任务可N次获得该Mutex,但是在N次释放前别的任务不可获得该Mutex。相关函数名在Mutex前有前缀Recursive。
事件组:允许任务在阻塞状态下等待多个组合事件;可在事件发生时unblock等待该事件(组)的所有任务,可以由多个任务共享。EventBits_t缺省为32bit,低24bit分别对应一个事件。以FreeRTOS+TCP TCP/IP stack包为例,它包括在FreeRTOS_Socket_t结构中,含有TCP socket的accept/bind/read/close及Abort事件,由FreeRTOS+TCP API使用。
相关函数为xEventGroupCreate、xEventGroupSetBits(FromISR)、xEventGroupWaitBits,最后这个函数允许所在任务按事件组blocked和unblocked。事件组也可用于任务同步,例如接受到TCP包的任务委托其它几个任务处理,在都处理完后相关事件都被置位,该包就可close。
Task Notifications任务通知:指任务间直接通信,不通过例如queue、semaphore等对象中转。将configUSE_TASK_NOTIFICATIONS置1后,每个任务都有“Notification State”和uint32的“Notification Value”。注意:发送任务不能阻塞以待发送完成。接收任务只允许是一个且不能是中断服务程序,可选指定时延的堵塞式等待它的通知状态变为Pending,在读取通知值后状态被置为Not-Pending。有两套函数:简化的发送xTaskNotifyGive()/xTaskNotifyGiveFromISR(),发送后通知值自动加1,配用堵塞等待的ulTaskNotifyTake()接收,可以取代以前的binary semaphore用于延迟的中断服务;增强的xTaskNotify()/xTaskNotifyFromISR() /xTaskNotifyWait(),多了对通知值的读写,可提供类似mail box或事件组覆盖写入的功能。xTaskNotifyWait()以可选的超时等待调用任务接收通知。如果接收任务在等待通知到达时已在等待通知的blocked态,则接收RTOS任务将被unblock并清除通知。
书中伪码例154:由Mutex互斥的xUART_Send()被共享来发送数据,先用xSemaphoreTake()清空,继而调用底层发送,再用xSemaphoreTake()使自己处于堵塞态。UART硬件在最后一字符被发送后中断,在中断服务中用xSemaphoreGiveFromISR()使得xUART_Send()解除堵塞,随即portYIELD_FROM_ISR()切换运行。
例155:semaphore带来的存储和执行开销、及初始化在上例中是不必要的。本例中semaphore由TaskHandle_t类型的xTaskToNotify取代,后者由API和中断共享。如果CPU位长不低于任务通知值,写入是原子操作,否则写入需临界域保护。用于接收的类似实现见例156。例157则用xTaskNotifyWait()以便传送ADC值,很实用的方法。本章最后说明了云端收发的流程。
关于调试:并非所有编译器都提供Assert,为此可在配置中定义等效的configASSERT(),出错时后续的代码不会运行。例如自定义函数vAssertCalled()然后定义:
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )
FreeRTOS提供Malloc failed hook、Stack overflow hook、Task Run-Time Statistics、Run-Time Statistics Clock,配置后与uxTaskGetSystemState()联用。另有Helper函数和Trace Hook Macros,许多IDE也提供debug plugin。FreeRTOS+Trace是Percepio提供的运行时诊断和优化工具,包括互联的20种图表。
最常见开发问题是incorrect interrupt priority assignment, stack overflow, inappropriate use of printf().
1)中断优先级:硬件中断的优先级应高于任务。不能超出configMAX_SYSCALL_INTERRUPT_PRIORITY,不能不定义,ARM Cortex把最低数字认为最高优先级。例如某CPU中断和配置最高中断都是5,但可被优先级4中断,则API中断优先级的值不应低于5。ARM Cortex的硬件和库实现都各有不同。优先级可分为pre-emption priority和sub-priority两部分,请只使用前一部分。有时候configMAX_SYSCALL_INTERRUPT_PRIORITY被称为configMAX_API_CALL_INTERRUPT_PRIORITY。
2)堆栈溢出:uxTaskGetStackHighWaterMark()返回某任务栈的最小剩余空间。配置configCHECK_FOR_STACK_OVERFLOW为1(返回栈的历史)或2(返回最近20此)并使用下列函数原型来勾取栈溢出的处理函数:void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName );
3)乱用printf()/sprintf():除非特殊设计。这两函数通常不具有线程安全性,可能会用malloc()或用堆栈,会加大程序尺寸和开销。开源的printf-stdarg.c可用来替代库函数,并可将输出重定向到端口。
其它注意事项:在中断服务中或调度器被挂起-例如在临界区域内-使用一般API函数可能崩盘。在调度器运行前的中断可能崩盘。在调度器工作前使用API可能导致中断被禁止。