运行层级:用户态 syscall指令切入 Ring0 内核;提供者:操作系统内核(Windows ntoskrnl.exe/ Linux 内核);形态:数字编号 + 寄存器传参,没有函数名; 特点:硬件级底层操作,无封装、参数原始、难用、跨版本不稳定; 作用:直接操作线程、内存、内核同步对象、磁盘、网卡硬件。
NtWaitForSingleObject、NtCreateEvent、NtWriteFile,靠数字编号调用。
Windows 原生 Win32 API( kernel32.dll/user32.dll);Ntdll 底层原生 API; CLR .NET 托管 API( AutoResetEvent、FileStream);第三方库 API。
运行层级:全程 Ring3 用户态,内部才会调用系统调用; 提供者:动态库、运行时、框架; 形态:有函数名、类型、友好参数,屏蔽底层细节; 特点:易用、做参数校验、版本兼容、异常封装。
C# 托管API(.NET类库) ↓ 封装调用Win32 API / Ntdll 用户态API ↓ 内部封装系统调用 Syscall(内核原始接口,syscall指令切入内核) ↓操作系统内核 Ring0 执行硬件操作
syscall / int CPU 指令,切换特权级 | ||
var are = new AutoResetEvent(false);are.WaitOne();
kernel32.dll 的 Win32 API:HANDLE CreateEventW(...);DWORD WaitForSingleObject(...);
Win32 API 对底层做了一层封装,屏蔽系统调用差异。
3. Win32 API 内部调用 ntdll 原生函数
WaitForSingleObject 内部跳转 ntdll.dll 的 NtWaitForSingleObject。syscall 指令,切换内核态,真正进入操作系统内核执行逻辑。
前三层全部是API(用户态函数封装); 最后一步才是系统调用,唯一切入内核的动作。
API 是 “工具箱的把手”,系统调用是 “直达发动机的传动轴”
你只需要拉把手(调用 API),不用手动转动传动轴(系统调用);把手内部自动联动传动轴。
API 可以完全不触发系统调用
很多纯内存计算 API 全程用户态,根本不会走 syscall:
Interlocked.Increment、Math.Sin、普通集合 Add、字符串处理;这类 API 只是纯内存逻辑,不需要操作系统内核介入,无系统调用。
一次 API 调用,可能触发 0 次、1 次、多次系统调用
纯计算 API:0 次 syscall; FileStream.Read:1 次读文件系统调用;WaitOne(1000):无信号阻塞,一次 syscall 挂起,唤醒又一次 syscall 返回,合计 2 次系统调用。
系统调用无法直接给业务代码使用 Windows 的 Nt 系列系统调用是非公开内部接口,系统更新会修改编号,直接调用会程序崩溃;微软强制开发者使用稳定 Win32/.NET API,屏蔽不稳定的原生系统调用。
误区 1:API = 系统调用
错误。API 是用户态函数,系统调用是 CPU 指令级内核交互;绝大多数 API 内部只是普通内存运算,根本不触发系统调用。
误区 2:只要调用 API 就会切换内核态
错误。只有 API 内部涉及硬件、线程阻塞、内存申请、IO 时,才会发起 syscall 切换内核;纯计算 API 全程用户态。
误区 3:Win32 API 就是系统调用
错误。Win32 是中间封装层,运行在用户态,只是一层壳,真正和内核沟通靠底层 ntdll 发起 syscall。
ConcurrentQueue.TryDequeue(.NET API)内部 CAS 自旋,纯用户态运算,无系统调用,不切换内核。AutoResetEvent.WaitOne(.NET API)内部封装 Win32 等待函数,最终发起系统调用,切换内核态阻塞线程。
syscall CPU 指令切换至内核态的底层原生交互通道,是程序访问操作系统硬件 / 内核资源的唯一方式;API 是上层封装外壳,系统调用是底层内核交互底层通道,二者是封装与被封装的从属关系。