此文是 ChatGPT 生成的文章帮助理解 : 用户态什么时候会切换到内核态在 Linux 中程序平时运行在用户态而内核代码运行在内核态。很多人会把“切换到内核态”理解成“用户程序一调用函数就进内核了”其实不是。真正会触发用户态到内核态切换的是系统调用、中断和异常。一、什么是切换到内核态CPU 运行程序时会处在不同的权限级别。用户程序运行在低权限的用户态不能直接操作硬件也不能直接执行内核函数。当程序需要操作文件、申请内存、访问设备、等待输入输出时就必须通过受保护的入口进入内核由内核代为完成。这个过程就是用户态 → 内核态 → 返回用户态二、不是“调用函数就切换”而是“发生特定事件才切换”很多常见函数名看起来像是在做系统级事情比如printf()malloc()memcpy()但它们本身不一定会切换到内核态。它们通常先在用户态完成自己的工作只有在内部确实需要内核帮助时才会发起系统调用进入内核态。例如malloc()先尝试在用户堆里分配不够时才通过brk()或mmap()向内核申请更多空间这时才发生用户态到内核态切换三、最常见的切换方式系统调用用户程序不能直接调用内核内部函数必须通过系统调用进入内核。例如read()write()open()ioctl()fork()mmap()这些函数的最终路径都会经过系统调用入口。系统调用的过程大致是用户程序调用 libc 接口这里的libc ,一般指 C 运行时库也就是常说的 C 标准库 / 标准库实现在 Linux 上最常见的是 glibc也可能是 uClibc、musl。它不只是 C 语言标准里的那些函数还包括很多POSIX 接口的封装。比如printf()malloc()free()strlen()strcpy()fopen()fread()fwrite()open()read()write()其中有些是纯库函数有些是对系统调用的封装。2. libc 发现需要进入内核3. 通过syscall、int 0x80、svc等指令触发切换4. CPU 进入内核态5. 内核执行对应服务函数6. 返回用户态四、除了系统调用中断和异常也会切换到内核态1. 中断比如定时器中断键盘中断网卡中断设备中断硬件一旦发出中断CPU 会暂停当前用户程序转去执行内核中的中断处理代码。2. 异常比如缺页异常page fault除零异常非法指令异常当程序访问了尚未映射的内存页CPU 也会进入内核由内核处理缺页再返回用户态继续执行。五、以 malloc() 为例理解切换过程#include stdio.h #include stdlib.h int main() { int *p malloc(100); printf(%p\n, p); free(p); return 0; }这个程序里malloc(100)表面上只是一个普通函数但它内部可能分两种情况情况一用户堆里还有空闲内存malloc()直接从用户态的内存管理器中分配不进入内核态。情况二用户堆不够了malloc()会通过系统调用向内核申请更多空间例如brk()mmap()这时 CPU 才会从用户态切换到内核态内核扩展进程虚拟地址空间返回用户态malloc()再把结果交给程序所以malloc 本身不一定切换内核态只有在需要向内核要资源时才会切换。六、用户态与内核态各自使用自己的栈用户栈每个线程有自己的用户栈保存函数调用、局部变量、返回地址程序结束时由内核自动回收内核栈每个线程进入内核态时使用自己的内核栈空间较小通常只有几 KB 到十几 KB不能随便放大数组或大对象也就是说切换到内核态时不只是权限变了连使用的栈也变了。七、 可以这样记忆用户程序并不是“想进内核就能进内核”而是必须通过正规入口系统调用最常见中断硬件触发异常错误或缺页触发而普通的用户态函数调用大多数仍然是在用户空间内部完成。八、一句话总结用户态切换到内核态发生在系统调用、中断和异常这些受保护事件中像malloc()、printf()这类函数本身不一定切换只有它们内部需要内核服务时才会真正进入内核态。--------------------------------------------------------------------------------------------------------------------下面是补充的知识点1.libc一般指C 运行时库也就是常说的C 标准库 / 标准库实现在 Linux 上最常见的是glibc也可能是 uClibc、musl。它不只是 C 语言标准里的那些函数还包括很多POSIX 接口的封装。比如printf()malloc()free()strlen()strcpy()fopen()fread()fwrite()open()read()write()其中有些是纯库函数有些是对系统调用的封装。2. libc 接口和系统调用是什么关系可以简单理解成这样用户程序 → libc 接口 → 系统调用 → 内核但要注意不是所有 libc 函数都会进入内核只有需要内核服务时才会触发系统调用例子printf()大多数时候只是用户态库函数先把内容放到缓冲区里。真正写到终端或文件时可能才会调用write()进而进入内核。malloc()先由 libc 自己管理堆内存。如果不够了才会调用brk()或mmap()之类的系统调用。3. 系统调用指的是什么系统调用是用户态 请求 内核服务 的入口。用户程序不能直接执行内核代码必须通过系统调用。常见系统调用有很多按功能大致可以分成几类文件和目录相关openclosereadwritelseekstatunlinkrename进程管理相关forkvforkexecveexitwaitpidgetpidkill内存管理相关brkmmapmunmapmprotect设备和控制相关ioctl时间相关nanosleepclock_gettimegettimeofday网络相关socketbindlistenacceptconnectsendrecv事件等待相关selectpollepoll_createepoll_ctlepoll_wait4. 一个很重要的区别库函数不等于系统调用例如printf()是库函数write()才是更接近内核的接口write()最终会触发系统调用再比如malloc()是库函数brk()/mmap()才会进入内核所以可以记成libc 是“用户态工具箱”系统调用是“进入内核的门”。5. 最容易混淆的一点有些函数名字看起来像系统调用但其实你平时调用的往往是libc 封装后的版本。比如int fd open(a.txt, O_RDONLY);你写的是open()但它通常是 libc 提供的包装内部再调用系统调用进入内核。open() 是 sys_open 的用户态薄封装只负责传参和触发系统调用真正打开文件的是内核里的 sys_open-------------------------------------------------------------------------------------------------------------Linux 内核和应用层这一块可以把它理解成下面这座三层楼对于 应用开发和驱动开发 来说可以这么理解用户程序↓库函数(libc)↓系统调用↓内核函数↓驱动函数↓硬件但如果再往下追内核函数↓驱动函数↓寄存器读写↓CPU总线↓硬件设备真正最底层的是readl();writel();或者gpio_set_value();最终变成CPU访问寄存器驱动开发本质上就是在做这件事。用户程序不能直接调用内核函数必须通过系统调用进入内核系统调用只是入口真正完成工作的仍然是内核中的各种函数和驱动函数。
Linux 触发用户态到内核态切换的是:系统调用、中断与异常
此文是 ChatGPT 生成的文章帮助理解 : 用户态什么时候会切换到内核态在 Linux 中程序平时运行在用户态而内核代码运行在内核态。很多人会把“切换到内核态”理解成“用户程序一调用函数就进内核了”其实不是。真正会触发用户态到内核态切换的是系统调用、中断和异常。一、什么是切换到内核态CPU 运行程序时会处在不同的权限级别。用户程序运行在低权限的用户态不能直接操作硬件也不能直接执行内核函数。当程序需要操作文件、申请内存、访问设备、等待输入输出时就必须通过受保护的入口进入内核由内核代为完成。这个过程就是用户态 → 内核态 → 返回用户态二、不是“调用函数就切换”而是“发生特定事件才切换”很多常见函数名看起来像是在做系统级事情比如printf()malloc()memcpy()但它们本身不一定会切换到内核态。它们通常先在用户态完成自己的工作只有在内部确实需要内核帮助时才会发起系统调用进入内核态。例如malloc()先尝试在用户堆里分配不够时才通过brk()或mmap()向内核申请更多空间这时才发生用户态到内核态切换三、最常见的切换方式系统调用用户程序不能直接调用内核内部函数必须通过系统调用进入内核。例如read()write()open()ioctl()fork()mmap()这些函数的最终路径都会经过系统调用入口。系统调用的过程大致是用户程序调用 libc 接口这里的libc ,一般指 C 运行时库也就是常说的 C 标准库 / 标准库实现在 Linux 上最常见的是 glibc也可能是 uClibc、musl。它不只是 C 语言标准里的那些函数还包括很多POSIX 接口的封装。比如printf()malloc()free()strlen()strcpy()fopen()fread()fwrite()open()read()write()其中有些是纯库函数有些是对系统调用的封装。2. libc 发现需要进入内核3. 通过syscall、int 0x80、svc等指令触发切换4. CPU 进入内核态5. 内核执行对应服务函数6. 返回用户态四、除了系统调用中断和异常也会切换到内核态1. 中断比如定时器中断键盘中断网卡中断设备中断硬件一旦发出中断CPU 会暂停当前用户程序转去执行内核中的中断处理代码。2. 异常比如缺页异常page fault除零异常非法指令异常当程序访问了尚未映射的内存页CPU 也会进入内核由内核处理缺页再返回用户态继续执行。五、以 malloc() 为例理解切换过程#include stdio.h #include stdlib.h int main() { int *p malloc(100); printf(%p\n, p); free(p); return 0; }这个程序里malloc(100)表面上只是一个普通函数但它内部可能分两种情况情况一用户堆里还有空闲内存malloc()直接从用户态的内存管理器中分配不进入内核态。情况二用户堆不够了malloc()会通过系统调用向内核申请更多空间例如brk()mmap()这时 CPU 才会从用户态切换到内核态内核扩展进程虚拟地址空间返回用户态malloc()再把结果交给程序所以malloc 本身不一定切换内核态只有在需要向内核要资源时才会切换。六、用户态与内核态各自使用自己的栈用户栈每个线程有自己的用户栈保存函数调用、局部变量、返回地址程序结束时由内核自动回收内核栈每个线程进入内核态时使用自己的内核栈空间较小通常只有几 KB 到十几 KB不能随便放大数组或大对象也就是说切换到内核态时不只是权限变了连使用的栈也变了。七、 可以这样记忆用户程序并不是“想进内核就能进内核”而是必须通过正规入口系统调用最常见中断硬件触发异常错误或缺页触发而普通的用户态函数调用大多数仍然是在用户空间内部完成。八、一句话总结用户态切换到内核态发生在系统调用、中断和异常这些受保护事件中像malloc()、printf()这类函数本身不一定切换只有它们内部需要内核服务时才会真正进入内核态。--------------------------------------------------------------------------------------------------------------------下面是补充的知识点1.libc一般指C 运行时库也就是常说的C 标准库 / 标准库实现在 Linux 上最常见的是glibc也可能是 uClibc、musl。它不只是 C 语言标准里的那些函数还包括很多POSIX 接口的封装。比如printf()malloc()free()strlen()strcpy()fopen()fread()fwrite()open()read()write()其中有些是纯库函数有些是对系统调用的封装。2. libc 接口和系统调用是什么关系可以简单理解成这样用户程序 → libc 接口 → 系统调用 → 内核但要注意不是所有 libc 函数都会进入内核只有需要内核服务时才会触发系统调用例子printf()大多数时候只是用户态库函数先把内容放到缓冲区里。真正写到终端或文件时可能才会调用write()进而进入内核。malloc()先由 libc 自己管理堆内存。如果不够了才会调用brk()或mmap()之类的系统调用。3. 系统调用指的是什么系统调用是用户态 请求 内核服务 的入口。用户程序不能直接执行内核代码必须通过系统调用。常见系统调用有很多按功能大致可以分成几类文件和目录相关openclosereadwritelseekstatunlinkrename进程管理相关forkvforkexecveexitwaitpidgetpidkill内存管理相关brkmmapmunmapmprotect设备和控制相关ioctl时间相关nanosleepclock_gettimegettimeofday网络相关socketbindlistenacceptconnectsendrecv事件等待相关selectpollepoll_createepoll_ctlepoll_wait4. 一个很重要的区别库函数不等于系统调用例如printf()是库函数write()才是更接近内核的接口write()最终会触发系统调用再比如malloc()是库函数brk()/mmap()才会进入内核所以可以记成libc 是“用户态工具箱”系统调用是“进入内核的门”。5. 最容易混淆的一点有些函数名字看起来像系统调用但其实你平时调用的往往是libc 封装后的版本。比如int fd open(a.txt, O_RDONLY);你写的是open()但它通常是 libc 提供的包装内部再调用系统调用进入内核。open() 是 sys_open 的用户态薄封装只负责传参和触发系统调用真正打开文件的是内核里的 sys_open-------------------------------------------------------------------------------------------------------------Linux 内核和应用层这一块可以把它理解成下面这座三层楼对于 应用开发和驱动开发 来说可以这么理解用户程序↓库函数(libc)↓系统调用↓内核函数↓驱动函数↓硬件但如果再往下追内核函数↓驱动函数↓寄存器读写↓CPU总线↓硬件设备真正最底层的是readl();writel();或者gpio_set_value();最终变成CPU访问寄存器驱动开发本质上就是在做这件事。用户程序不能直接调用内核函数必须通过系统调用进入内核系统调用只是入口真正完成工作的仍然是内核中的各种函数和驱动函数。