高频易混淆概念辨析

共 19 题
📑 题目列表 19 题
#
★★★

1. 进程与线程、协程与线程、异步与非阻塞、并发与并行的辨析如何一句话讲清?

如何用一句话讲清进程与线程、协程与线程、异步与非阻塞、并发与并行这几组易混淆概念的区别?

  • 理解进程、线程、协程的资源与切换模型
  • 掌握异步与非阻塞的语义差异
  • 理解并发与并行的本质区别

一句话讲清:进程是资源分配(内存、文件、地址空间)的基本单位,线程是 CPU 调度的基本单位,同一进程内多个线程共享地址空间;协程是用户态可主动让出/恢复的轻量级执行单元,挂载在线程上,切换由程序控制而非内核抢占,成本远低于线程。异步是"不等待结果、通过回调/事件通知完成",非阻塞是"调用不阻塞当前线程、立即返回",异步可以基于非阻塞实现。并发是多个任务在时间段内交错执行(逻辑上同时),并行是多个任务在同一时刻真正同时执行(物理上同时,需多核)。

关键区分维度:进程/线程看"资源与调度的归属",协程/线程看"切换由谁控制(内核 vs 用户态)",异步/非阻塞看"是否等待结果 vs 是否阻塞调用",并发/并行看"逻辑同时 vs 物理同时"。用这些维度能一句话讲清每组概念。

#
★★★

2. 同步/异步与阻塞/非阻塞的四象限组合在实际 API(NIO/AIO/协程)中的体现?

同步/异步与阻塞/非阻塞的四象限组合在实际 API 中如何体现,例如 NIO、AIO 与协程各自属于哪一类?

  • 理解同步/异步与阻塞/非阻塞两个维度
  • 掌握 NIO(非阻塞)、AIO(异步)的模型
  • 理解协程与 IO 模型的关系

同步/阻塞:调用方发起 IO 后一直等待,直到数据就绪并返回,如传统 BIO 的 read/write。同步/非阻塞:调用立即返回,但调用方需轮询检查是否就绪,如 NIO 的非阻塞模式需要配合多路复用(select/poll/epoll)避免 CPU 空转。异步/阻塞:少见,一般指发起异步操作后仍阻塞等待完成通知。异步/非阻塞:调用立即返回,数据就绪后由系统通过回调/事件通知,如 AIO(异步 IO)与基于事件循环的协程模型。协程通常把非阻塞 IO 封装成"同步写法",底层用事件循环调度,让阻塞调用变成可挂起的协程,从而用同步式代码获得异步的高并发。

两个维度正交:同步/异步看"结果如何获得(主动取 vs 通知)",阻塞/非阻塞看"调用是否立即返回"。NIO 是同步非阻塞,AIO 是异步非阻塞,BIO 是同步阻塞,协程是"把非阻塞 IO 用同步写法表达"的编程模型。

#
★★★

3. 中断(interrupt)与异常(exception)的区分,trap、fault、abort 三类异常的语义差异与典型例子如何?

如何区分中断(interrupt)与异常(exception),并说明 trap、fault、abort 三类异常的语义差异与典型例子?

  • 理解中断与异常的本质区别(外部异步 vs 内部同步)
  • 掌握 trap、fault、abort 的语义差异
  • 理解异常返回后是否可重试

中断是外部硬件异步事件(如时钟、IO、键盘),与当前指令无关,内核对当前指令处理后转去处理中断;异常是 CPU 执行指令时同步产生的内部事件(如除零、缺页、非法指令),与指令本身相关。异常分三类:trap(陷阱)是主动触发的,如系统调用 int 0x80,指令执行后转内核,返回后继续下一条指令;fault(故障)是可恢复的,如缺页、页错误,返回后重新执行触发该异常的指令;abort(中止)是不可恢复的严重错误,如硬件错误、非法指令,程序通常终止,无法重试。

关键在于"同步/异步"与"是否可重试"。中断异步、与指令无关;异常同步、与指令相关。trap 主动、fault 可恢复(重试)、abort 不可恢复(终止)。系统调用用 trap,缺页用 fault,致命错误用 abort。

#
★★

4. 中断与异常、内核态与用户态、系统调用与库函数的分层关系?

中断与异常、内核态与用户态、系统调用与库函数之间存在怎样的分层关系?

  • 理解用户态与内核态(特权级)的切换
  • 掌握系统调用与中断/异常的关系
  • 理解库函数对系统调用的封装

用户态与内核态是 CPU 特权级(如 x86 的 ring0/ring3)的划分,用户态受限、内核态可访问所有资源。用户程序需要访问硬件或内核资源时,通过系统调用(如 read、write、open)触发 trap 异常,CPU 从用户态切换到内核态执行内核代码,完成后返回用户态。系统调用本质上是"主动触发的异常"(trap)。库函数(如 C 的 printf、malloc)在用户态实现,往往对系统调用做了封装与缓冲:printf 可能调用 write 系统调用,malloc 用 brk/mmap 系统调用申请内存。因此分层是:应用 → 库函数 → 系统调用(trap)→ 内核态执行。

分层主线是"用户态代码 → 库函数 → 系统调用 → 陷入内核"。中断是被动外部事件,系统调用是主动 trap,两者都是进入内核态的途径。库函数是用户态工具,系统调用是用户态与内核态的接口。

#
★★

5. 共享内存与消息队列、管道与 Socket 的通信模型差异?

共享内存与消息队列、管道与 Socket 等进程间通信(IPC)方式的模型差异是什么?

  • 理解共享内存与消息队列的数据模型差异
  • 掌握管道与 Socket 的通信语义
  • 理解不同 IPC 的适用场景

共享内存是两进程映射同一块物理内存,读写最快、无系统调用开销,但需自行处理同步互斥(信号量/锁);消息队列是内核维护的消息链表,进程通过 send/recv 传递结构化消息,有同步保证但需拷贝,较慢。管道(pipe)是半双工、单向、基于字节流的父子进程通信,基于内核缓冲;命名管道(FIFO)可用于任意进程。Socket 是跨主机/跨网络的传输层通信(TCP/UDP),基于字节流或数据报,适合分布式通信,成本较高。模型差异体现为:共享内存"共享内存+同步"、消息队列"消息传递+同步"、管道"字节流单向"、Socket"网络字节流/数据报"。

对比维度是"数据共享 vs 消息传递""是否需同步""是否跨进程/跨主机""实时性"。共享内存最快但最复杂,消息队列简单可靠但慢,管道适合父子进程单向流,Socket 适合网络。选择取决于实时性、跨主机需求与复杂度。

#
★★

6. 字节序(大端/小端)与网络字节序,序列化时的常见坑?

大端/小端字节序与网络字节序的区别是什么,序列化时有哪些常见坑?

  • 理解大端与小端的内存布局
  • 掌握网络字节序(大端)的约定
  • 识别跨平台序列化的字节序问题

大端(big-endian)是最高有效字节存在低地址,小端(little-endian)是最低有效字节存在低地址,x86 是小端,网络协议统一采用大端字节序(网络字节序)。序列化坑:一是跨平台/跨架构(如 x86 小端与 ARM 大端)传输原始内存数据时字节序不一致导致数值错误,需用 htonl/ntohl 或按网络字节序序列化;二是直接 memcpy 结构体或内存前加类型字段,不同平台的对齐(padding)与字节序导致解析失败;三是 C/C++ 中联合体/位域依赖字节序,Java 等语言自动处理但与大端外部协议交互时需注意。解决:序列化时统一字节序,用显式协议(如 protobuf、JSON)或明确 byte order 字段。

核心是"同一数据在不同字节序机器上内存布局不同"。网络字节序约定大端,跨平台序列化必须显式转换或采用与字节序无关的编码方式,避免直接拷贝原始内存。

#
★★

7. 并发(Concurrency)与并行(Parallelism)的区别,单核并发、多核并行的经典辨析题如何?

并发(Concurrency)与并行(Parallelism)的区别是什么?为什么单核可以并发但不能并行?

  • 理解并发与并行的本质区别
  • 掌握单核并发与多核并行的关系
  • 理解不同场景的应用选择

并发是多个任务在同一时间段内交错执行,关注的是"任务结构"与对资源的合理利用,单核通过时间片切换也能实现并发;并行是多个任务在同一时刻真正同时执行,需要多核或多 CPU 从物理上同时运行多个任务。单核只能并发(时间片交错的逻辑同时),不能并行(无法在同一时刻物理执行多个任务)。并发是并行的基础,并行是并发在硬件多核上的物理实现。在高并发 IO 场景常用并发(如事件循环、协程)处理大量等待,在计算密集场景用并行(多核)加速。

一句话:并发是"同时处理多件事(逻辑上)",并行是"同时执行多件事(物理上)"。单核靠时间片并发,多核才能并行。区分意义在于:IO 密集用并发提升吞吐,计算密集用并行利用多核。

#
★★

8. 进程与线程、协程的关系,内核态线程 vs 用户态协程的切换成本与适用场景如何?

进程、线程与协程的关系如何?内核态线程与用户态协程的切换成本与适用场景有何差异?

  • 理解进程、线程、协程的资源层级
  • 掌握内核线程与用户协程切换成本差异
  • 理解协程的适用场景

进程是资源分配单位,线程是内核调度单位(内核线程),协程是用户态定义、由用户程序调度器调度的轻量级执行单元,一个线程可调度多个协程。切换成本:线程切换需进入内核态,涉及保存/恢复寄存器、栈切换、上下文切换与可能的缓存失效,成本高;协程切换在用户态完成,只需保存/恢复少量寄存器与栈,无系统调用,成本极低(纳秒级)。适用场景:协程适合高并发 IO 密集场景(大量等待、大量并发任务),如网络代理、爬虫、微服务 IO 处理;线程适合 CPU 密集或需要内核抢占、多核并行与系统级隔离的场景。协程不适合计算密集的并行(无法利用多核),需配合线程才能用满多核。

核心差异是"切换由谁控制、成本多高"。内核线程切换进内核态、成本高;用户态协程切换在用户态、成本低。协程解决"高并发下大量线程切换开销"问题,但并行计算仍需多线程/多进程。

#
★★

9. IO 多路复用与多线程阻塞 IO 的辨析,什么场景下多路复用更优,什么场景下多线程更简单?

IO 多路复用与多线程阻塞 IO 各自的适用场景是什么?什么情况下多路复用更优,什么情况下多线程更简单?

  • 理解 IO 多路复用(select/poll/epoll)的模型
  • 掌握多线程阻塞 IO 的模型
  • 能根据连接数、并发度与开发复杂度选择方案

IO 多路复用用单个线程/少量线程同时监听大量 fd 的就绪事件(epoll),当连接数大、单个连接大多处于等待状态(如长连接、大量空闲连接)时,多路复用能显著减少线程数量与切换开销,更优。多线程阻塞 IO 为一个连接/请求分配一个线程,阻塞在 read/write 上,模型简单、易开发调试,适合连接数少、连接基本活跃、并发量不高的场景,也为某个请求独立处理提供天然隔离。多路复用的问题是实现复杂、回调式编程、单线程内不能有阻塞操作;多线程的问题是大并发下线程数膨胀、上下文切换与内存开销大。选择依据:连接数多且空闲多 → 多路复用;连接数少、逻辑简单、追求可读性 → 多线程。

本质是"用多少线程支撑多少连接"。多路复用以"少量线程 + 事件驱动"支撑海量连接,适合高并发长连接;多线程以"一连接一线程"换取简单,适合低并发。现代方案常结合两者(如 Netty 的 Reactor 多线程 + 多路复用)。

#
★★

10. 并发安全与原子性的辨析,为什么加锁不一定保证原子性(check-then-act),CAS 的 ABA 问题属于哪类?

并发安全与原子性如何辨析?为什么加锁不一定保证原子性(check-then-act),CAS 的 ABA 问题属于哪类问题?

  • 理解并发安全与原子性的区别
  • 掌握 check-then-act 竞态
  • 理解 CAS 与 ABA 问题

并发安全是"多个线程并发访问共享数据时结果正确",原子性是"一系列操作要么全部执行要么全部不执行,不可中断"。加锁能保证互斥,但若临界区内做的是"先检查再操作"(check-then-act),如"if(余额足够) 扣款",虽加锁保证同时只有一个线程进入,但若检查与操作之间状态被外部改变(如锁内检查、锁外操作),或锁保护范围不完整,仍可能产生竞态。原子性要求"检查+操作"作为一个不可分割的整体。CAS 的 ABA 问题是:CAS 比较并交换时,若值从 A 变为 B 又变为 A,CAS 会误判没有变化而成功更新,属于"条件检查(比较)与更新之间状态被篡改"的竞态问题,即一种 check-then-act 的时序问题,不是原子性,而是需要版本号/标记位解决。

加锁解决"互斥访问",原子性解决"操作整体性"。check-then-act 是竞态根源,即使加锁也要保证检查与操作在同一个原子操作内。ABA 是 CAS 的"比较"不可靠,需用带版本号的原子引用(AtomicStampedReference)解决。

#
★★

11. 奇偶校验、校验和(checksum)、CRC 与海明码(Hamming code)在检错/纠错能力上的本质区别是什么,冗余位与码距如何决定能力上限,为什么以太网 FCS 选择 CRC-32 检错而 ECC 内存选择 SEC-DED 海明码纠错?

奇偶校验、校验和、CRC 与海明码在检错/纠错能力上的本质区别是什么?冗余位与码距如何决定能力上限,为什么以太网 FCS 用 CRC-32 检错而 ECC 内存用 SEC-DED 海明码纠错?

  • 理解奇偶校验、校验和、CRC、海明码的算法差异
  • 掌握码距与检错/纠错能力的关系
  • 理解 CRC-32 检错与海明码纠错的选择依据

四者本质区别在检错/纠错能力与代价:奇偶校验只加 1 个冗余位,能检出奇数个错误,无法纠错且无法发现偶数个错误;校验和(checksum)把数据按字求和,能检出部分错误但存在进位/置换未检测的局限,不能纠错;CRC 用多项式除法生成冗余码,检错能力强(能检测突发错误、可检测任意长度突发错误直到某个长度),但不纠错;海明码通过冗余位构成校验矩阵,能检错并纠错(能纠正 1 位错误、检测 2 位错误)。码距决定能力:若码距为 d,则能检 d-1 位错误、纠正 (d-1)/2 下取整位错误。冗余位越多、码距越大,纠错能力越强但开销越大。以太网 FCS 用 CRC-32 是因为其强检错能力足以覆盖帧传输错误(几乎不会只想纠错),且开销小、实现简单;ECC 内存用 SEC-DED 海明码(能纠正 1 位、检测 2 位)是因为内存错误主要是单比特翻转,需要纠错而非仅检错,从而维持数据正确性。

核心是"用多少冗余换多少检错/纠错能力"。检测与纠错本质不同:检错只发现错误(需重传/重读),纠错直接修复。CRC 检错强、代价低,适合网络检错后重传;海明码能纠错,适合内存这种无法重传、必须就地修复的场景。

#
★★

12. 软链接与硬链接、文件描述符与文件句柄的差异?

软链接与硬链接、文件描述符与文件句柄各自有何差异?

  • 理解软链接与硬链接的本质区别
  • 掌握文件描述符(fd)与文件句柄的语义
  • 理解不同系统的实现差异

软链接(符号链接)是一个独立文件,存的是目标路径,指向另一个文件,可跨文件系统、指向目录、目标删除后软链接失效(悬空);硬链接是同一个 inode 的多个目录项,共享同一个文件内容与 inode,删除一个链接不影响其他(只要链接数 >0),但不能跨文件系统、不能指向目录。文件描述符(fd)是 POSIX/Unix 中用户态访问文件的整数句柄,指向内核的打开文件描述(open file description),包含文件偏移、访问模式等;文件句柄(file handle)在不同语境含义不同,在 Windows 中 handle 泛指操作系统资源句柄,在 Unix 常指 fd 或内核中的 file 结构。fd 是"打开的实例",链接是"目录项与 inode 的映射",二者维度不同。

软/硬链接看"inode 是否共享":硬链接共享 inode,软链接是独立路径。fd 与句柄看"如何访问已打开的文件"。注意区分"链接(目录项)"与"打开的文件实例(fd)"。

#
★★

13. 并发与并行、进程与线程的辨析?

并发与并行、进程与线程这两组概念如何辨析?它们之间有何联系?

  • 理解并发与并行的区别
  • 理解进程与线程的区别
  • 掌握二者的联系与适用场景

并发与并行:并发是逻辑上同时处理多个任务(单核时间片即可),并行是物理上同时执行(需多核)。进程与线程:进程是资源分配/隔离的基本单位,拥有独立地址空间;线程是进程内执行与调度的基本单位,共享进程地址空间与资源,切换比进程轻量。联系:一个进程可含多个线程,多线程在多核上可并行,在单核上并发;进程间通信(IPC)较线程间(共享内存)开销大。辨析核心:并发/并行讲"时间上如何交错或同时",进程/线程讲"资源的边界与执行单位"。设计上,IO 密集常用多线程/协程并发,计算密集用多进程/多线程并行利用多核。

两组概念正交:并发/并行描述任务执行的时间关系,进程/线程描述资源组织与调度的实体。多线程既能并发也能并行,取决于核数。辨析时先分维度,再讲联系。

#
★★

14. 虚拟内存/物理内存/交换空间的区分?

虚拟内存、物理内存与交换空间(swap)三者如何区分?

  • 理解虚拟内存的地址空间抽象
  • 掌握物理内存与虚拟内存的映射
  • 理解交换空间的作用

虚拟内存是操作系统为每个进程提供的独立、连续的地址空间抽象,进程见到的地址是虚拟地址,通过页表映射到物理内存;物理内存是真实的 RAM 硬件,是虚拟地址最终落地的地方。交换空间(swap)是磁盘上划出的区域,当物理内存不足时,把不常用的页换出到磁盘,腾出物理内存,是"后备存储"。三者关系:虚拟内存决定"进程能看到的地址空间大小",物理内存是"实际存放",swap 是"内存不足时的溢出区"。虚拟内存大于物理内存时,靠多级页表 + swap 支撑。swap 的代价是磁盘访问远慢于内存,频繁换页会导致性能骤降(thrashing)。

一句话:虚拟内存是"逻辑空间",物理内存是"实际空间",swap 是"磁盘当内存用"。虚拟内存带来隔离与保护,swap 提供内存不足时的缓冲,但性能代价高。

#
★★

15. 栈与堆的区别,内存布局、分配速度、生命周期与大小限制,为什么递归过深会栈溢出而大数组常在堆上?

栈与堆在内存布局、分配速度、生命周期与大小限制上有何区别?为什么递归过深会栈溢出而大数组常在堆上?

  • 理解栈与堆的内存布局
  • 掌握栈与堆的分配速度与生命周期差异
  • 理解递归栈溢出与大数组放堆的原因

栈是连续内存、由编译器自动管理,分配入栈只需移动栈指针,速度极快;生命周期由作用域决定,函数返回自动释放;栈大小有限(通常几 MB,由系统/线程栈上限决定)。堆是运行时通过 malloc/new 分配的不连续内存,分配需查找空闲块、触发系统调用,速度较慢;生命周期由程序员控制(需手动释放或 GC);堆大小较大(受地址空间/物理内存限制)。递归每层调用都占用栈帧(局部变量、返回地址),深度过大时栈空间耗尽即栈溢出;大数组若在栈上分配会迅速耗尽有限的栈空间,因此应放在堆上(或用 static/new 分配),堆有更大的空间。

栈"快、自动、小、生命期自动",堆"慢、手动、大、生命期自由"。递归栈溢出是栈有限,大数组放堆是堆空间大。选择依据是生命周期与大小。

#
★★

16. 强类型/弱类型与静态/动态类型,类型检查时机的两个维度如何组合,与运行时性能、重构安全的关系如何?

强类型/弱类型与静态/动态类型如何组合?类型检查时机与运行时性能、重构安全有何关系?

  • 理解强/弱类型与静态/动态类型的维度
  • 掌握类型检查发生时机(编译期 vs 运行期)
  • 理解类型系统与性能、重构安全的关系

强/弱类型描述"类型是否会被隐式强制转换",强类型(如 Java、Python)不做隐式危险转换,弱类型(如 C、JS)允许隐式转换;静态/动态类型描述"类型检查发生在编译期还是运行期",静态(Java、C++、Go、TypeScript)编译期检查,动态(Python、Ruby、JS)运行期检查。静态类型在编译期发现类型错误,运行时省去类型检查,性能更好,且重构安全(改类型编译器报错);动态类型灵活、开发快,但类型错误在运行时才暴露,重构风险高,运行时需做类型判断。四者组合:如 Java 强+静态、C 弱+静态、Python 强+动态、JS 弱+动态。

两个维度正交:强/弱看"类型转换的严格性",静态/动态看"检查时机"。静态类型更早、更安全、性能好;动态类型更灵活。性能与重构安全取决于静态类型性,隐式转换风险取决于弱类型。

#
★★

17. 值传递与引用传递的辨析,Java/C#/C++/Go 中的"引用"语义差异,为什么"一切皆引用"的说法不准确?

值传递与引用传递如何辨析?Java/C#/C++/Go 中的"引用"语义有何差异,为什么"一切皆引用"的说法不准确?

  • 理解值传递与引用传递(按引用)的本质
  • 掌握不同语言中"引用"的语义差异
  • 理解 Java 对象引用按值传递的真相

值传递是传递实参的一个拷贝,函数内修改不影响实参;按引用传递是传递实参的地址/引用,函数内修改会影响实参。Java 的参数传递全部是值传递:对基本类型拷贝值,对对象拷贝的是"对象引用"的值(即引用按值传递),所以函数内修改引用指向的对象会影响外部对象,但重新赋值引用不会影响外部。C++ 支持真正的引用传递(T&)与指针。C# 有值类型与引用类型,参数可用 ref/out 传递引用。Go 的参数默认也是值传递,但切片、map、channel 等内部含指针,函数内修改会影响底层。说"一切皆引用"不准确,因为 Java 中对象是引用类型但"引用本身"是按值传递的,且基本类型是值类型,二者语义不同。

关键区分"传的是值的拷贝"还是"传的地址/引用"。Java 的"引用"是对象句柄,传参时复制的是这个句柄的值,因此是值传递。不同语言借用"引用"一词含义不同,需按具体语义理解。

#
★★

18. 会话(Session)与连接(Connection)的区别,TCP 连接、HTTP 会话与业务会话的生命周期与状态边界如何?

会话(Session)与连接(Connection)如何区分?TCP 连接、HTTP 会话与业务会话的生命周期与状态边界有何不同?

  • 理解连接(传输层)与会话(逻辑层)的区别
  • 掌握 TCP 连接、HTTP 会话、业务会话的生命周期
  • 理解各层状态边界与关联

连接是传输层(TCP)的物理/逻辑通信通道,有明确的建立、传输、关闭状态机(三次握手、四次挥手),生命周期由网络层决定;会话是更高层的逻辑状态,指一系列相关交互的上下文。TCP 连接是底层字节流通道,生命周期短且由网络事件(建立/关闭)决定;HTTP 会话(如 HTTP/1.1 keep-alive 复用同一 TCP 连接上的多次请求)强调连接复用与请求/响应配对;业务会话(如登录后的 Session、购物车状态)是应用层维护的用户级上下文,通过 Cookie/Token 关联,生命周期由业务逻辑(登录、超时、登出)决定,可跨越多个 TCP 连接。一层连接承载多个业务会话,或一个业务会话跨越多个连接。状态边界:TCP 连接有内核状态机,HTTP 会话无连接状态但请求可复用,业务会话是应用内存/存储中的状态。

连接是"传输层通道",会话是"逻辑状态"。TCP 连接生命周期由网络决定,业务会话由应用逻辑决定,二者松耦合。理解"连接复用会话、会话跨连接"是重点。

#

19. 用户态与内核态的切换,系统调用与陷阱如何触发?

用户态与内核态之间的切换是如何发生的?系统调用与陷阱(trap)有什么关系?

  • 理解用户态与内核态的特权级
  • 掌握系统调用通过 trap 陷入内核的机制
  • 理解切换的代价与开销

用户态与内核态是 CPU 特权级划分,用户态受限、内核态可访问全部资源。切换通过异常/中断触发:系统调用是程序主动发起,通过 trap 指令(如 syscall/int 0x80)触发,CPU 保存现场(用户态寄存器、栈指针),切换到内核态执行相应内核处理函数,再恢复现场返回用户态;外部中断(时钟、IO)也会被动触发切换。切换过程涉及保存/恢复寄存器、切换栈、刷新 TLB 与缓存等,代价较高,因此频繁系统调用会降低性能。系统调用是"用户态主动请求进入内核态"的陷阱,是实现"用户态无法直接访问硬件"时的安全接口。

系统调用是主动 trap 进入内核态的方式,中断是被动事件。切换的本质是"改变特权级并保存/恢复现场",成本在于寄存器、栈、缓存等开销。理解这一点能解释为何要减少系统调用、为何 syscall 比普通函数慢。