Linux系统概念
Linux 系统概述
Linux,这个诞生于个人兴趣的开源操作系统,如今已成为服务器、嵌入式设备乃至个人电脑领域的主力军。其免费、开源、稳定、高效等特点,使其在全球范围内拥有庞大的用户群体。
什么是 Linux?
Linux 并非一个单一的软件,而是一个操作系统内核。这个内核负责管理系统硬件、进程调度、内存分配等底层任务。Linux 的开源特性使得全球开发者共同参与其开发,不断完善和创新。
Linux 的特点
- 开源:任何人都可以自由获取、使用、修改和分发 Linux 的源代码。
- 稳定性:Linux 在长时间运行下表现出极高的稳定性,适合用于构建可靠的服务器。
- 安全性:相较于其他操作系统,Linux 的安全性更高,这得益于其开源的特性和社区的共同努力。
- 可定制性:Linux 可以高度定制,以满足各种不同的需求。用户可以通过配置内核、安装不同的软件包来构建个性化的系统。
- 社区:Linux 拥有全球最大的开源社区,提供丰富的文档、教程和支持。
Linux 的发展历史
从 Unix 到 Linux
Linux 的诞生受到了 Unix 操作系统的深远影响。Unix 的哲学、设计理念和工具集为 Linux 提供了坚实的基础。Linus Torvalds 在大学期间,出于对 Unix 的热爱和对当时商业操作系统的不满,决定开发一个属于自己的操作系统。
Linux 的主要版本和发行版
- Linux 内核:Linux 内核是 Linux 系统的心脏,负责管理系统资源。主要版本有 2.x、3.x 和 4.x 等。
- Linux 发行版:为了方便用户使用,Linux 社区衍生出了众多发行版,如 Ubuntu、CentOS、Fedora、Debian 等。每个发行版都有自己的特点和目标用户。
Linux 的核心概念
内核(Kernel)
- 内核的作用:内核是系统与硬件之间的桥梁,负责管理系统的所有资源。
- 内核态与用户态:内核运行在内核态,拥有最高的权限;用户程序运行在用户态,权限受限。
- 系统调用:用户程序通过系统调用来请求内核执行特权操作。
Shell
Shell 的概念:Shell 是一个命令解释器,用户通过 Shell 与系统进行交互,对用户输入到终端的命令进行解析,调用对应的执行程序。
用户在终端输入命令,由shell命令解释器对命令进行解析(按照$PATH环境变量搜索命令),解析成内核能够识别的指令,然后由内核执行命令,最后由终端显示命令执行的结果给用户。
注意
shell在寻找命令的时候是按照$PATH环境变量去查找的,如果找到了就执行对应的命令,若找不到就报错,执行echo $PATH可以查看PATH环境变量的值。
常用的 Shell:Bash、Zsh、Fish 等。Bash 是最常用的 Shell,提供了强大的命令和脚本功能。
shell -- Bourne Shell
shell/bin/sh
bash -- Bourne Again Shell
shell/bin/bash
当前系统所使用的shell
echo \$SHELL
当前系统下有哪些shell
cat /etc/shells
切换shell
chsh -s shell的路径
# 例如
chsh -s /bin/zsh
文件系统
文件系统的概念:文件系统是组织和管理文件的方式。 Linux 常用的文件系统:ext4、XFS、Btrfs 等。ext4 是目前最常用的文件系统,具有良好的性能和可靠性。
用户管理
用户和组:Linux 系统中的用户和组用于控制对系统资源的访问权限。 用户管理命令:useradd、userdel、passwd 等。
进程管理
进程的概念:进程是正在运行的程序。 进程管理命令:ps、top、kill 等。
Linux 的基本架构
Linux 系统的层次结构
Linux 系统可以分为内核层、系统调用层、库函数层和用户程序层。
Linux 的启动过程
Linux 系统的启动过程大致分为 BIOS 加载、Bootloader 加载内核、内核初始化、启动用户界面等几个阶段。
Linux 与其他操作系统的区别
Linux 与 Windows 的对比:Linux 开源、稳定、安全,而 Windows 商业化、易用。 Linux 与 macOS 的对比:Linux 和 macOS 都基于 Unix,但 Linux 更开放,macOS 更封闭。
环境变量
环境变量:存储在操作系统中的全局键值对(key-value),用于定义进程的运行环境,影响应用程序的行为。
作用域:
- 全局环境变量:对所有用户和进程有效(如
/etc/profile
) - 用户环境变量:仅当前用户生效(如
~/.bashrc
) - 临时环境变量:仅当前终端会话有效
核心作用
环境变量 | 作用场景 | 典型值示例 |
---|---|---|
PATH | 程序搜索路径 | /usr/bin:/home/user/bin |
HOME | 用户主目录 | /home/user |
LD_LIBRARY_PATH | 动态库加载路径 | /usr/local/lib |
LANG | 语言环境设置 | en_US.UTF-8 |
PWD | 当前工作目录 | /home/user/project |
操作命令(shell层面)
- 查看所有环境变量:bash
env # 显示所有环境变量 printenv # 同上
- 查看特定变量:bash
echo $PATH # 输出PATH变量值
- 临时设置变量:bash
export MY_VAR="value" # 全局环境变量 MY_VAR="value" # 仅当前shell有效
- 永久设置变量:bash
echo "export MY_VAR=value" >> ~/.bashrc # 用户级 sudo echo "export MY_VAR=value" >> /etc/profile # 系统级 source ~/.bashrc # 使配置生效
- 删除变量:bash
unset MY_VAR
在C/C++程序中访问
#include <cstdlib>
#include <iostream>
int main() {
char* path = std::getenv("PATH");
if (path) {
std::cout << "PATH: " << path << std::endl;
}
return 0;
}
常见应用场景
- 配置搜索路径:将自定义程序目录添加到PATH
- 动态库依赖:设置LD_LIBRARY_PATH解决找不到.so文件的问题
- 语言国际化:通过LANG控制字符编码
- 脚本开发:在shell脚本中引用环境变量(如
$HOME
)
注意事项
- 变量名区分大小写:
Path
≠PATH
- 优先级:用户变量 > 系统变量
- 安全性:避免在环境变量中存储敏感信息(如密码)
- 继承性:子进程会继承父进程的环境变量
扩展知识
- 环境变量与shell变量的区别:
- 环境变量可被子进程继承,shell变量仅限当前shell
- 环境变量通过
export
声明
- 环境变量配置文件:
/etc/profile
(系统级全局)~/.bash_profile
(用户登录时加载)~/.bashrc
(交互式shell加载)
- 特殊变量:
$0
:当前脚本名称$?
:上一条命令的退出状态码$$
:当前进程ID
"环境变量是Linux系统中配置运行环境的核心机制,通过定义全局或局部的键值对,控制程序的搜索路径、语言环境、资源依赖等。掌握环境变量的查看、设置、删除方法,以及在C++程序中通过getenv
访问,是Linux开发的必备技能。在实际应用中需注意变量作用域、继承性和安全性,合理利用环境变量提升开发效率。"
总结
Linux 作为一款功能强大、灵活可定制的操作系统,在服务器、嵌入式系统、云计算等领域有着广泛的应用。学习 Linux 不仅可以提升计算机系统的理解,还可以掌握更多强大的工具,为未来的发展打下坚实的基础。
拓展阅读
- Linux 官方网站:https://www.kernel.org/
- Ubuntu 官方网站:https://www.ubuntu.com/
- CentOS 官方网站:https://www.centos.org/
Linux下常用快捷键
tab键的作用
补齐命令
如:在终端输入his然后按tab键,会补齐history命令;
如:输入l然后按tab键,会显示所有以l开头的命令.
补齐文件(包括目录和文件)
例如: 如果在执行ls,然后按tab键,会显示当前目录下所有的文件
主键盘快捷键
遍历输入的历史命令
- 从当前位置向上遍历:ctrl + p(↑)
- 从当前位置向下遍历:ctrl + n(↓)
注意:使用history命令可以显示用户输入的所有命令。
光标位置移动
- 光标左移: ctrl + b (←)
- 坐标右移: ctrl + f (→)
- 移动到头部: ctrl + a(home)
- 移动到尾部: ctlr + e(end)
字符删除
删除光标前边的字符:ctrl + h(Backspace)
删除光标后边的字符:ctrl + d
光标后边的字符即光标覆盖的字符,例如
执行该命令,删除的是字符W
删除光标前所有内容:ctrl + u
删除光标后所有内容:ctrl + k
man手册
在使用Linux系统及进行Linux系统编程的时间,有任何不懂的地方都可以查阅man手册进行学习。使用命令man man
可以阅读man手册的帮助文档
man手册的内容
该手册分为多卷:第一卷是用来查看 shell
命令的;第二卷是用来查看系统调用相关信息的;第三卷是用来查看 库函数信息的...
1 可执行程序或 shell 命令
2 系统调用(内核提供的函数)
3 库调用(程序库中的函数)
4 特殊文件(通常位于 /dev)
5 文件格式和规范,如 /etc/passwd
6 游戏
7 杂项(包括宏包和规范,如 man(7),groff(7))
8 系统管理命令(通常只针对 root 用户)
9 内核例程 [非标准]
man
命令的格式如下:
$ man [手册编号] cmd
比如,我们可以这样用:
$ man man
$ man 3 mkdir
进入帮助界面后,我们可以按下面按键浏览帮助信息:
d(down): 往下翻半页
u(up): 往上翻半页
f(forward): 往下翻一整页
b(backward):往上翻一整页
q(quit): 退出
如果遇到查询库函数查询不到的情况需要先,安装以后就可以进行查看了
sudo apt install manpages-posix-dev -y
Tips
我们还可以查看指定语言的man手册条目,在这之前要先确定我们系统中已经安装的语言
locale -a
# 输出示例
➜ ~ locale -a
C
C.utf8
POSIX
en_US.utf8
zh_CN.utf8
➜ ~
我们可以使用以下命令来指定要查看哪种语言的man手册条目
man -Lzh_CN man # 中文
man -Len_US man # 英文
man -Len man # 英文
如果是未知语言,会自动fallback到系统默认语言。其实前面的locale -a只是起到确定-L参数的值的作用,不在里面的其实也可以,只要安装了对应的man pages,比如意大利语: man -Lit man
man手册阅读步骤
先阅读名字
看声明和返回值
- -->
- -->
- -->
细节 按需查看
在线man手册
Debian - 有些文档是中文的
Arch Linux - 格式更加美观
结语
本文仅对 Linux 系统的概念进行了初步的介绍,Linux 的世界博大精深,还有很多值得探索的领域。希望本文能激发你对 Linux 的兴趣,鼓励你深入学习。
面试题
内核态和用户态的区别是什么?
内核态和用户态的区别主要体现在三个方面:
- 权限:用户态程序受限,无法直接操作硬件;内核态拥有最高权限。
- 内存访问:用户态仅能访问用户空间,内核态可访问全部地址。
- 切换机制:通过系统调用、中断或异常触发切换。
以C++的new
操作为例,常规分配在用户态完成,但内存不足时会触发内核态的系统调用。在网络编程中,频繁切换会带来性能损耗,可通过零拷贝技术优化。理解这些机制有助于设计高性能后端系统。
Details
在C++后端开发岗位的面试中,回答“内核态和用户态的区别”需要从底层原理、实际编程场景和性能优化等多个维度展开,以下是系统性回答建议:
一、核心区别与原理
权限级别
• 用户态:应用程序运行时的受限模式,CPU只能执行非特权指令(如普通运算),无法直接访问硬件设备或内核数据结构。例如,用户态程序无法直接操作网卡或磁盘,必须通过系统调用委托内核完成。
• 内核态:操作系统内核运行的最高权限模式,CPU可执行所有指令(包括特权指令),能直接访问内存、硬件设备(如修改页表、响应中断)。例如,处理缺页异常时,内核会直接修改进程的内存映射表。内存访问范围
• 用户态仅能访问用户空间(如Linux的0x00000000-0xBFFFFFFF),而内核态可访问全部虚拟地址空间(包括内核共享的3GB-4GB区域)。切换触发机制
• 系统调用:用户程序主动请求内核服务(如文件读写、网络通信),通过syscall
指令触发软中断(如x86的int 0x80
)。
• 硬件中断:外部设备触发(如网卡收到数据包),CPU暂停用户态进程,执行中断处理程序。
• 异常:执行非法操作(如除零错误、页缺失)时自动切换到内核态处理。
二、C++开发中的实际场景
动态内存分配(
new
操作符)
• 常规内存分配(如小对象)由用户态的内存池(如malloc
管理的堆)完成,无需切换内核态。
• 当用户态堆内存不足时,会通过brk
或mmap
系统调用扩展虚拟内存,此时触发内核态切换。例如分配超大数组int* arr = new int[1e9]
可能触发此过程。网络编程优化
• 频繁的read/write
系统调用会产生切换开销,高性能场景可通过零拷贝技术(如sendfile
)或用户态协议栈(如DPDK)减少切换次数。
三、回答加分点
切换成本分析
• 上下文切换需保存寄存器、程序计数器等状态,并切换内核栈,通常需要数百到数千CPU周期。在IO密集型系统中,频繁切换可能成为性能瓶颈。优化策略
• 批量处理:合并系统调用(如writev
替代多次write
)。
• 内存池设计:预分配大块内存减少内核态切换(参考tcmalloc/jemalloc
原理)。
系统调用和库函数的区别是什么?
系统调用是操作系统内核提供的底层接口(如open()
),需切换到内核态执行,直接操作硬件但开销大;库函数(如fopen()
)运行于用户态,封装了系统调用以提升易用性,适合高频或跨平台场景。两者在权限、性能、可移植性上各有优劣,实际开发中常结合使用。例如,printf()
最终通过write()
输出,但添加了缓冲优化。
Details
系统调用(System Call)与库函数(Library Function)是程序与操作系统交互的两种不同机制,它们在功能、执行层级、性能开销等方面存在显著差异。以下是两者的核心区别与联系:
一、定义与执行层级
系统调用
• 定义:由操作系统内核提供的接口,允许用户程序请求内核服务(如文件操作、进程管理、硬件控制等)。
• 执行层级:运行于内核态(Kernel Mode),需要从用户态切换到内核态,涉及中断和上下文切换。
• 示例:open()
(打开文件)、fork()
(创建进程)、write()
(写入数据)。库函数
• 定义:由编程语言或第三方库提供的函数,封装了常用功能(如字符串处理、数学运算等),可能间接调用系统调用。
• 执行层级:运行于用户态(User Mode),不涉及内核态切换。
• 示例:fopen()
(文件打开)、printf()
(格式化输出)、strcpy()
(字符串复制)。
二、核心区别
对比维度 | 系统调用 | 库函数 |
---|---|---|
权限与安全性 | 直接访问硬件和内核资源,安全性由内核保障 | 仅能访问用户空间资源,安全性较低 |
性能开销 | 需用户态/内核态切换,开销较大(微秒级) | 无状态切换,开销小(纳秒级) |
可移植性 | 依赖操作系统内核,移植性差 | 通过封装屏蔽底层差异,跨平台兼容性好 |
功能封装 | 直接操作硬件或内核资源,功能单一 | 可能整合多个系统调用,提供更高级的抽象(如缓冲I/O) |
三、具体场景分析
文件操作
• 系统调用:open()
直接打开文件,返回文件描述符,无缓冲。
• 库函数:fopen()
封装了open()
,并添加缓冲区管理,减少频繁读写开销。网络通信
• 系统调用:socket()
创建套接字,send()
发送原始数据。
• 库函数:printf()
可能调用write()
,并处理格式化字符串。性能权衡
• 系统调用适用于需直接操作硬件的场景(如设备驱动开发)。
• 库函数适合高频调用或需要跨平台的通用功能(如字符串处理)。
四、联系与协作
依赖关系:部分库函数通过封装系统调用实现功能。例如:
•fread()
内部调用read()
。
•malloc()
可能使用sbrk()
调整堆内存。互补性:
• 系统调用提供底层能力,库函数优化易用性(如缓冲机制)。
• 开发者需根据场景选择:直接系统调用(高性能需求) vs. 库函数(开发效率)。
通过理解两者的差异与协作机制,开发者能更高效地平衡性能与开发复杂度。
系统调用和函数调用的开销
系统调用开销远高于函数调用,核心原因是用户态/内核态切换、权限检查、数据拷贝和上下文保存。单次系统调用耗时约200ns~15μs(是函数调用的几十倍),且涉及数千条CPU指令。优化手段包括缓冲批量处理(如writev
)、零拷贝技术(如mmap
)和异步I/O(如io_uring
)。例如,高并发日志系统通过批量写入将系统调用次数降低90%。
Details
系统调用与函数调用的开销差异主要体现在用户态与内核态的切换、权限检查、上下文保存/恢复等环节。以下是两者的核心区别及开销分析:
一、开销差异的核心原因
用户态与内核态切换
• 系统调用:需要从用户态切换到内核态,涉及CPU特权级别(如x86的Ring 3到Ring 0)和页表的切换。此过程需要保存用户态寄存器和栈信息,切换到内核栈,并触发中断处理程序(如int 0x80
或syscall
指令),导致CPU流水线中断和缓存失效。
• 函数调用:完全在用户态执行,仅涉及用户栈的压栈、跳转指令(如call
),无需特权切换或上下文保存,CPU缓存命中率高。权限检查与数据安全性
• 系统调用:需验证参数有效性(如内存地址是否属于用户空间)、执行权限等安全机制,可能导致多次内存访问和条件判断。
• 函数调用:无内核权限检查,参数传递和内存访问仅需用户态校验,效率更高。数据拷贝与内存访问
• 系统调用:输入/输出参数需在用户空间和内核空间之间拷贝(如read
将数据从内核缓冲区复制到用户缓冲区),涉及额外的内存操作。
• 函数调用:参数通过栈或寄存器直接传递,无跨空间拷贝开销。中断与上下文切换
• 系统调用:触发软中断或syscall
指令,需保存/恢复完整的执行上下文(如SS、ESP、EFLAGS寄存器),导致CPU流水线刷新和TLB失效。
• 函数调用:仅需保存返回地址和局部寄存器,无中断处理流程。
二、量化对比
时间开销
• 系统调用:单次耗时约 200ns~15μs(具体因系统调用类型和硬件而异)。例如:空系统调用(如getpid
)约200ns,带数据拷贝的系统调用(如read
)可达微秒级。
• 函数调用:单次耗时约 1~10ns,仅为系统调用的 1/20~1/100。CPU指令数
• 系统调用:单次需执行数千条指令(如权限检查、中断处理、数据拷贝)。实验显示,100万次read
调用消耗约 10亿条指令。
• 函数调用:通常仅需几十条指令(如参数压栈、跳转、返回值处理)。上下文切换开销
• 系统调用:涉及用户态与内核态的完整上下文切换,单次消耗约 500~2000 CPU周期。
• 函数调用:无上下文切换,仅需处理栈帧变化。
三、优化策略(减少系统调用开销)
缓冲与批量处理
• 使用缓冲区(如fopen
代替open
)或批量操作(如writev
聚合多个write
调用),减少调用次数。
• 示例:高并发日志系统中,批量写入4KB数据而非单次写入小数据,可降低90%的系统调用次数。零拷贝技术
• 使用mmap
或splice
避免数据在用户空间与内核空间之间的冗余拷贝。用户态替代方案
• 利用vdso
(Virtual Dynamic Shared Object)在用户态直接执行部分系统调用(如gettimeofday
),避免切换内核态。异步与非阻塞调用
• 使用epoll
或io_uring
实现异步I/O,减少阻塞等待时间。
四、实际场景对比
场景 | 系统调用 | 函数调用 |
---|---|---|
文件读写 | read /write 需跨空间拷贝和权限检查 | fread /fwrite 利用用户态缓冲减少调用 |
网络通信 | send /recv 触发协议栈处理和软中断 | 用户态协议栈(如DPDK)绕过内核开销 |
高并发日志系统 | 频繁write 导致CPU sys态占用40%+ | 双缓冲队列+批量写入优化至5%以下 |