【温故知新】Linux系统启动流程(BIOS+GRUB模式)

一、硬件初始化阶段
1、电源自检(POST)
按下电源键,主板 BIOS 固件启动,依次检测 CPU、内存、硬盘控制器、显卡、外设等核心硬件,故障则通过蜂鸣 / 屏幕提示终止启动,无异常则进入下一步。
2、BIOS 固件初始化,定位启动设备并加载GRUB第一扇区
BIOS 加载自身固化驱动,初始化已检测通过的硬件,建立基础硬件运行环境,识别本地存储设备(硬盘/SSD)。BIOS 按 CMOS 中预设的启动顺序(硬盘/U盘/光驱),找到目标启动硬盘,读取硬盘主引导记录(MBR,磁盘首个 512 字节),加载其中的 GRUB 第一阶段(Stage1)引导程序,将控制权移交 GRUB。

二、引导加载阶段
3、GRUB Stage1 执行
MBR 中的 Stage1 程序无完整驱动,仅负责定位并加载硬盘中 **/boot 分区 ** 的 GRUB Stage1.5 程序(位于 MBR 之后、第一个分区之前的空闲扇区)。
4、GRUB Stage1.5 加载
加载 Stage1.5 并初始化,其集成了文件系统驱动(ext4/xfs 等),可直接识别 /boot 分区的文件系统,无需依赖其他程序。
5、GRUB Stage2 加载
通过 Stage1.5 读取 /boot/grub 目录下的完整 GRUB 程序(Stage2),加载 GRUB 核心模块,完成引导程序自身初始化。
6、内核加载准备
读取 /boot/grub/grub.cfg 配置文件,多系统场景显示启动菜单(超时选默认项),将 Linux 内核镜像(vmlinuz)、初始内存盘(initramfs/initrd)加载至物理内存,向内核传递根分区位置、启动参数等信息。

三、内核启动阶段
7、内核解压与初始化
内核在内存中自解压并运行,初始化 CPU、内存管理、进程调度、中断处理等内核核心子系统。
8、加载临时驱动与文件系统
挂载 initramfs 为临时根文件系统,加载硬盘、文件系统等内核原生未集成的必要驱动,为挂载真实根分区做准备。
9、挂载根文件系统
通过临时驱动识别并以只读模式挂载真实根文件系统(ext4/btrfs/xfs 等)。
10、切换根文件系统
从 initramfs 临时根切换至真实根分区,释放 initramfs 占用的内存资源。
11、启动第一个用户进程
内核启动首个用户空间进程(主流为 systemd,传统为 init),PID 固定为 1,内核将系统控制权完全移交用户空间,内核启动阶段完成。

四、用户空间初始化
12、初始化系统启动
systemd 读取核心配置文件(/etc/systemd/system/default.target),确定系统默认启动目标。
13、启动基础服务
按服务依赖关系并行启动基础核心服务:udev(硬件动态管理)、日志服务(journald/rsyslog)、/etc/fstab 配置的非根分区挂载(并将根分区从只读改为读写)等。
14、启动目标单元服务
根据默认启动目标(multi-user.target 命令行 /graphical.target 图形界面),启动对应服务组(如网络、SSH、定时任务等)。
15、登录界面 / Shell 就绪
启动字符终端 getty 进程或图形登录管理器(GDM/LightDM),显示登录提示 / 可视化登录界面,Linux 系统启动完成,进入可操作状态。

关键差异
1、BIOS 无 EFI 系统分区(ESP),依赖 MBR 加载引导程序,而 UEFI 直接从 ESP 加载 grubx64.efi。
2、BIOS 下 GRUB 为多阶段加载(Stage1→Stage1.5→Stage2),解决 MBR 空间不足(仅 512 字节)无法存储完整引导程序的问题;UEFI 下 GRUB 为单阶段直接加载。

引导方式 BIOS+GRUB UEFI+GRUB
固件类型 传统 BIOS 固件(固化在主板,功能简单) 新式 UEFI 固件(模块化,功能丰富)
引导分区 无专用分区,依赖硬盘 MBR(512 字节) 专用 EFI 系统分区(ESP,FAT32 格式)
GRUB加载方式 多阶段(Stage1→1.5→2)加载 单阶段直接加载 grubx64.efi 文件
启动项存储 存储在主板 CMOS 中(掉电易丢失) 存储在 ESP 分区的 EFI 启动项中(更稳定)
硬件支持 最大支持 2TB 硬盘(MBR 分区表限制) 支持大于 2TB 硬盘(GPT 分区表)

【温故知新】Linux系统启动流程(UEFI+GRUB模式)

一、硬件初始化阶段
1、电源自检(Power-On Self-Test, POST)
按下电源键后,主板 UEFI 固件执行核心硬件检测(内存、CPU、硬盘控制器、显卡等),硬件故障则终止启动并抛出提示,无异常则进入下一阶段。
2、固件初始化
UEFI 固件加载自身驱动,识别本地存储设备(硬盘 / SSD 等),初始化硬件运行环境,完成启动前基础准备。
3、启动管理器加载
UEFI 按预设启动项顺序,从EFI 系统分区(ESP) 读取并加载 GRUB 引导程序核心文件(grubx64.efi)。

二、引导加载阶段
4、GRUB 第一阶段加载
UEFI 将系统控制权移交 GRUB,加载 GRUB 核心运行模块,完成引导程序自身初始化。
5、GRUB 配置文件解析
读取 /boot/grub/grub.cfg 配置文件,解析内核路径、启动参数,多系统场景显示启动菜单(超时后选默认项)。
6、内核加载准备
根据配置将 Linux 内核镜像(vmlinuz)、初始内存盘(initramfs/initrd)加载至物理内存,向内核传递根分区位置等关键启动参数。

三、内核启动阶段
7、内核解压与初始化
内核在内存中自解压并运行,初始化 CPU、内存管理、进程调度、中断处理等内核核心子系统。
8、加载临时驱动与文件系统
挂载 initramfs 为临时根文件系统,加载硬盘、文件系统等内核原生未集成的必要驱动,为挂载真实根分区做准备。
9、挂载根文件系统
通过临时驱动识别并以只读模式挂载真实根文件系统(ext4/btrfs/xfs 等)。
10、切换根文件系统
从 initramfs 临时根切换至真实根分区,释放 initramfs 占用的内存资源。
11、启动第一个用户进程
内核启动首个用户空间进程(主流为 systemd,传统为 init),PID 固定为 1,内核将系统控制权完全移交用户空间,内核启动阶段完成。

四、用户空间初始化
12、初始化系统启动
systemd 读取核心配置文件(/etc/systemd/system/default.target),确定系统默认启动目标。
13、启动基础服务
按服务依赖关系并行启动基础核心服务:udev(硬件动态管理)、日志服务(journald/rsyslog)、/etc/fstab 配置的非根分区挂载(并将根分区从只读改为读写)等。
14、启动目标单元服务
根据默认启动目标(multi-user.target 命令行 /graphical.target 图形界面),启动对应服务组(如网络、SSH、定时任务等)。
15、登录界面 / Shell 就绪
启动字符终端 getty 进程或图形登录管理器(GDM/LightDM),显示登录提示 / 可视化登录界面,Linux 系统启动完成,进入可操作状态。

Windows进程间通讯全解析:10大核心方式+场景选型,搞定进程协同

Windows进程间通讯


Windows进程间通讯全解析:10大核心方式+场景选型,搞定进程协同

在 Windows 系统中,多个进程想要协同工作(比如浏览器调用下载工具、办公软件同步数据),就离不开 “进程间通讯(IPC)” 技术。不同场景下,有的需要高速传输大数据,有的需要实时同步状态,有的要跨网络通讯 —— 选对 IPC 方式,能让程序协作更高效、更稳定。今天就拆解 Windows 系统中常见的进程间通讯方式,帮你理清适用场景和核心逻辑。

一、内核对象同步:轻量级状态协同,保障进程有序执行
核心逻辑:利用 Windows 内核对象的信号状态,实现进程间的同步与互斥(比如避免多个进程同时操作同一资源),适用于简单状态通知。
代表技术:互斥量(Mutex,保证同一时刻只有一个进程访问资源)、信号量(Semaphore,控制同时访问资源的进程数量)、Event(事件对象,通过信号触发进程执行);
优势:轻量级、效率高、系统原生支持,无需复杂配置;
适用场景:进程间同步(如多个进程读写同一文件时的锁机制)、简单状态通知(如 “任务完成” 信号触发下一个进程执行)。

二、共享内存类:高速传输大数据,性能优先之选
核心逻辑:多个进程共享同一块物理内存,直接读写内存实现数据传输,无需拷贝,是速度最快的 IPC 方式。
代表技术:共享内存(直接创建共享内存区域,进程间直接访问)、文件映射(将文件映射到内存,多个进程通过内存共享文件数据)、DLL 共享段(通过 DLL 的共享数据段,实现进程间数据共享);
优势:传输速度极快(内存级读写)、无数据拷贝损耗、支持大容量数据传输;
注意点:需要自己实现同步机制(如搭配互斥量),避免数据竞争;
适用场景:高频大数据传输(如视频处理软件的帧数据传递、工业软件的实时数据共享)。

三、管道通讯:基于文件系统,适配本地进程交互
核心逻辑:模拟文件读写的方式,通过 “管道” 这一伪文件实现进程间字节流传输,是 Windows 原生的本地 IPC 方案。
代表技术:匿名管道(Pipe,仅支持父子进程或亲缘进程间通讯,单向传输)、命名管道(Named Pipe,支持任意本地进程间通讯,双向传输);
优势:系统原生支持、使用简单、传输可靠;
适用场景:本地进程间字节流传输(如命令行工具的输出传递、本地服务与客户端的通讯)。

四、组件对象模型:跨进程调用,实现功能复用
核心逻辑:通过 COM(组件对象模型)、DCOM(分布式 COM)、OLE 技术等,将一个进程的功能封装为组件,供其他进程远程调用,实现功能复用。
代表技术:COM(本地跨进程组件调用,如 Office 组件嵌入其他软件)、DCOM(跨网络的 COM 调用,支持远程组件访问)、OLE(对象链接与嵌入,如文档中嵌入图片、表格);
优势:封装性好、支持功能复用、接口标准化;
适用场景:跨进程功能调用(如第三方组件集成、软件插件扩展)、分布式系统的远程组件访问。

五、网络通讯类:跨机器 / 广域通讯,突破本地限制
核心逻辑:基于网络协议实现进程间通讯,不仅支持本地进程,还能跨 Windows 机器、跨网络通讯,是最通用的 IPC 方式。
代表技术:Socket(套接字,支持 TCP/UDP 协议,本地与跨网络通讯通用,如客户端 / 服务器架构)、NetBios 函数(早期 Windows 网络通讯接口,支持局域网内进程交互);
优势:通用性强、支持跨机器 / 跨网络、适配各类数据传输场景;
适用场景:网络应用(如浏览器与服务器通讯)、跨机器进程协同(如分布式服务间调用)、客户端 / 服务器架构软件。

六、消息与钩子:Windows 原生机制,适配桌面应用交互
核心逻辑:利用 Windows 的消息机制或钩子函数,实现进程间的消息传递或行为监控。
代表技术:消息通知(Windows 消息队列,进程间发送自定义消息)、钩子函数(Hook,监控或拦截其他进程的消息 / 行为,如键盘钩子、鼠标钩子)、DLL 注入(通过注入 DLL,实现进程间消息传递或功能扩展);
优势:深度适配 Windows 桌面应用、支持行为监控与消息触发;
适用场景:桌面应用交互(如窗口间消息通知)、进程行为监控(如安全软件的行为拦截)、软件功能增强(如通过 DLL 注入扩展第三方软件功能)。

七、中间件 / 存储介质:间接通讯,解耦进程依赖
核心逻辑:通过第三方存储介质或中间件传递数据,进程间不直接交互,降低耦合度,适配复杂场景。
代表技术:文件(通过读写同一文件传递数据,如日志同步、配置共享)、数据库(关系型 / 非关系型数据库,如多进程共享业务数据)、缓存中间件(Redis、etcd、zk,分布式场景下的配置同步与数据共享)、消息队列(MQ,异步通讯,如任务分发、数据异步传递);
优势:解耦进程依赖、支持异步通讯、适配分布式场景;
适用场景:分布式系统(如微服务间通讯)、异步任务处理(如批量数据处理)、跨进程配置共享(如多进程读取同一数据库配置)。

八、其他特色方式:适配特殊场景需求
除了主流方式,还有一些针对特定场景的 IPC 方案:
动态数据交换(DDE):早期 Windows 桌面应用的通讯方式,支持应用间数据实时同步(如 Excel 表格数据同步到 Word 文档);
粘贴板(Clipboard):简单直观的进程间数据传递,用户通过复制粘贴实现(如从浏览器复制文本到记事本);
文件传输协议(FTP):通过 FTP 协议实现文件级别的进程间 / 跨机器数据传输,适用于大容量文件共享。

总结:Windows IPC 选型核心逻辑 ——“按需匹配,兼顾效率与场景”
选择 IPC 方式时,核心看 3 个维度:
传输距离:本地进程选共享内存、管道、COM;跨网络选 Socket、中间件;
数据量与速度:大数据高速传输选共享内存、文件映射;小数据同步选内核对象、消息通知;
交互方式:同步通讯选 Socket、命名管道;异步通讯选 MQ、中间件;功能复用选 COM/DCOM。
Windows 的 IPC 方案覆盖了从本地轻量级同步到分布式跨网络通讯的全场景,掌握不同方式的核心逻辑,就能根据项目需求精准选型,让进程协同更高效。

你在开发中常用哪种 Windows IPC 方式?遇到过哪些兼容性或性能问题?欢迎在评论区留言交流~

WSL2中apt升级systemd时报错:无法锁定passwd文件

1、环境:
Windows10+WSL2+Ubuntu24
PS:另一台电脑Windows11+WSL2+Ubuntu24,不会报错

2、再现方式及错误信息

# apt-get upgrade
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Calculating upgrade... Done
...
...
Setting up systemd (255.4-1ubuntu8.8) ...
Initializing machine ID from random generator.
Failed to take /etc/passwd lock: Invalid argument
dpkg: error processing package systemd (--configure):
installed systemd package post-installation script subprocess returned error exit status 1
Errors were encountered while processing:
systemd
E: Sub-process /usr/bin/dpkg returned an error code (1)

3、错误发生原因
systemd升级的脚本,会调用systemd-sysusers,systemd-sysusers会尝试通过fcntl锁定文件,但WSL中fcntl实现效果与Linux中不同,导致脚本执行失败。
更进一步的解释:
Linux中文件锁是基于文件描述符的,子进程会自动继承该文件锁。
Windows中文件锁是基于进程的,子进程需要自行获取新的文件锁。
WSL中,实现方式,更接近与Windows,重复获取同一个文件的锁自然是失败的。

openat(AT_FDCWD, "/etc/.pwd.lock", O_WRONLY|O_CREAT|O_NOCTTY|O_NOFOLLOW|O_CLOEXEC, 0600) = 3
fcntl(3, F_OFD_SETLKW, {l_type=F_WRLCK, l_whence=SEEK_SET, l_start=0, l_len=0}) = -1 EINVAL (Invalid argument)

4、如何绕过该错误

# 原文在此:https://github.com/microsoft/WSL/issues/10397

# 切换到/bin
# 将systemd-sysusers修改为systemd-sysusers.org
# 将systemd-sysusers做成一个echo的符号链接(用于欺骗升级脚本,让其以为得到了正确的结果)
# 切换回之前的目录
cd /bin && mv -f systemd-sysusers{,.org} && ln -s echo systemd-sysusers && cd -

# 修复包依赖
apt --fix-broken install

# 继续升级
apt-get upgrade

Linux虚拟化管理

1、内核模块初始化

module_init(vmx_init)->kvm_init
module_init(svm_init)->kvm_init

//其中,kvm_init
->kvm_arch_init
->kvm_irqfd_init
->kvm_arch_hardware_setup
->misc_register(&kvm_dev)

2、从数据结构角度,又可以看到了设备皆为文件的思想

static struct miscdevice kvm_dev = {
    KVM_MINOR,
    "kvm",
    &kvm_chardev_ops,
};

static struct file_operations kvm_chardev_ops = {
    .unlocked_ioctl = kvm_dev_ioctl,
    .llseek     = noop_llseek,
    KVM_COMPAT(kvm_dev_ioctl),
};

//初始化时,通过misc_register,实现了操作的绑定。

3、通过上面的数据结构,我们就可以找到创建虚拟机的方法,并生成控制文件
kvm_dev.kvm_chardev_ops.kvm_dev_ioctl
或者,ioctl系统调用KVM_CREATE_VM,效果也是一样的:

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
->vfs_ioctl,会用到vfs_ioctl.unlocked_ioctl也就是kvm_dev_ioctl

->case KVM_CREATE_VM:
->        r = kvm_dev_ioctl_create_vm(arg);
->file = anon_inode_getfile("kvm-vm", &kvm_vm_fops, kvm, O_RDWR);

//其中,kvm_dev_ioctl_create_vm
->kvm_create_vm
->->kvm_arch_init_vm
->->hardware_enable_all
->->kvm_arch_post_init_vm
->->list_add(&kvm->vm_list, &vm_list);

4、生成虚拟CPU套路很相似,仍是文件操作

static struct file_operations kvm_vm_fops = {
    .release        = kvm_vm_release,
    .unlocked_ioctl = kvm_vm_ioctl,
    .llseek     = noop_llseek,
    KVM_COMPAT(kvm_vm_compat_ioctl),
};

//创建虚拟机时,通过anon_inode_getfile,实际上就把文件和kvm_vm_fops绑定了起来。
anon_inode_getfile("kvm-vm", &kvm_vm_fops, kvm, O_RDWR)

5、在调用ioctl时

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
->vfs_ioctl,会用到vfs_ioctl.unlocked_ioctl也就是kvm_vm_ioctl

kvm_vm_ioctl->kvm_vm_ioctl_create_vcpu
->kvm_arch_vcpu_precreate
->kvm_vcpu_init
->kvm_arch_vcpu_create
->kvm_get_kvm
->create_vcpu_fd,生成设备文件inode
->kvm_arch_vcpu_postcreate

//其中,kvm_arch_vcpu_create
->kvm_mmu_create
->vcpu->arch.user_fpu = kmem_cache_zalloc(x86_fpu_cache, GFP_KERNEL_ACCOUNT);
->kvm_pmu_init(vcpu);
->kvm_hv_vcpu_init(vcpu);
->kvm_x86_ops.vcpu_create(vcpu);
->kvm_vcpu_mtrr_init(vcpu);
->vcpu_load(vcpu);
->kvm_vcpu_reset(vcpu, false);
->kvm_init_mmu(vcpu, false);  //包括init_kvm_tdp_mmu和init_kvm_softmmu两种虚拟化方式

6、启动虚拟机,还是文件操作

static struct file_operations kvm_vcpu_fops = {
    .release        = kvm_vcpu_release,
    .unlocked_ioctl = kvm_vcpu_ioctl,
    .mmap           = kvm_vcpu_mmap,
    .llseek     = noop_llseek,
    KVM_COMPAT(kvm_vcpu_compat_ioctl),
};

7、在调用ioctl时KVM_RUN

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
->vfs_ioctl,会用到vfs_ioctl.unlocked_ioctl也就是kvm_vcpu_ioctl

kvm_vcpu_ioctl->
case KVM_RUN: 
  kvm_arch_vcpu_ioctl_run

//其中,
kvm_arch_vcpu_ioctl_run->vcpu_run->vcpu_enter_guest

8、IO同样有虚拟化和半虚拟化两种
一个处理函数为kvm_fast_pio,另一个为kvm_emulate_instruction

Linux系统调用03

Linux系统调用的整体流程为:
1、应用程序【用户态】通过syscall或glibc进行内核功能调用,这一部分在glibc源码中进行的
2、CPU收到syscall,Linux内核响应syscall调用【内核态】,这一部分在linux源码中进行的
3、返回结果到应用程序【用户态】

本节,给Linux系统,增加一个新系统调用功能,获取cpu数量。

1、新建一个源码编译目录

mkdir kernelbuild

2、下载源码,解压

wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.10.59.tar.gz   

tar -xzf linux-5.10.59.tar.gz   

cd linux-5.10.59

3、清理

make mrproper

4、修改文件

4.1、arch/x86/entry/syscalls/syscall_64.tbl
#在440后面增加一行
441  common  get_cpus    sys_get_cpus

4.2、include/linux/syscalls.h
#在最后一个asmlinkage增加一行
asmlinkage long sys_get_cpus(void);

4.3、kernel/sys.c  
#在最后一个SYSCALL_DEFINE0后面增加下面几行
//获取系统中有多少CPU
SYSCALL_DEFINE0(get_cpus)
{
    return num_present_cpus();
}

5、内核配置

make menuconfig
make oldconfig

6、修改.config,去掉一个证书

CONFIG_SYSTEM_TRUSTED_KEYS=“”

7、编译

make -j4

8、安装

sudo make modules_install
sudo make install

9、测试
9.1、新建文件cpus.c

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
int main(int argc, char const *argv[])
{
    //syscall就是根据系统调用号调用相应的系统调用
    long cpus = syscall(441);
    printf("cpu num is:%d\n", cpus);//输出结果
    return 0;
}

9.2、编译

gcc main.c -o cpus

9.3、运行

./cpus
在没有修改的内核上返回是-1
在修改过的为num_present_cpus数量

极客时间 操作系统实战45讲 Linux系统调用源码

Linux系统调用02

Linux系统调用的整体流程为:
1、应用程序【用户态】通过syscall或glibc进行内核功能调用,这一部分在glibc源码中进行的
2、CPU收到syscall,Linux内核响应syscall调用【内核态】,这一部分在linux源码中进行的
3、返回结果到应用程序【用户态】
本节处理第二部分:

二、linux内核部分

1、在make时,会通过syscall_64.tbl生成syscalls_64.h,然后包含到syscall_64.c,进行调用号与函数之间的绑定。
arch/x86/entry/syscalls/syscall_64.tbl
arch/x86/include/generated/asm/syscalls_64.h
arch/x86/entry/syscall_64.c

1.1、以sys_openat为例,在syscall_64.tbl中为

257    common    openat            sys_openat
441    common    get_cpus          sys_get_cpus

1.2、make后,在生成的syscalls_64.h中为

__SYSCALL_COMMON(257, sys_openat)

1.3 在syscall_64.c中,展开__SYSCALL_COMMON

#define __SYSCALL_COMMON(nr, sym) __SYSCALL_64(nr, sym)
//展开就是
__SYSCALL_64(257, sys_openat)

1.4、在syscall_64.c中,第一次展开__SYSCALL_64

#define __SYSCALL_64(nr, sym) extern long __x64_##sym(const struct pt_regs *);
#include <asm/syscalls_64.h>
#undef __SYSCALL_64

//展开就是
extern long __x64_sys_openat(const struct pt_regs *);

//也就是每个__SYSCALL_64都展开成了一个外部函数

1.5、在syscall_64.c中,第二次展开__SYSCALL_64

#define __SYSCALL_64(nr, sym) [nr] = __x64_##sym,

asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
    [0 ... __NR_syscall_max] = &__x64_sys_ni_syscall,
#include <asm/syscalls_64.h>
};

//展开其实就是指向了外部函数
[257]=__x64_sys_openat,

//全部展开结果,都会被包含到sys_call_table中,从而完成了调用号与函数之间的绑定。

2、当产生系统调用时
2.1、应用直接syscall或通过glibc产生了syscall

2.2、cpu会产生类似于中断的效果,开始到entry_SYSCALL_64执行

//文件路径arch/x86/entry/entry_64.S
SYM_CODE_START(entry_SYSCALL_64)
//省略代码
call   do_syscall_64
SYM_CODE_END(entry_SYSCALL_64)

//文件路径arch/x86/entry/entry_64.S,32位兼容模式,过程与64位类似
SYM_CODE_START(entry_SYSCALL_compat)
call   do_fast_syscall_32
SYM_CODE_END(entry_SYSCALL_compat)

2.3、调用do_syscall_64

#ifdef CONFIG_X86_64
__visible noinstr void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
    nr = syscall_enter_from_user_mode(regs, nr);
    instrumentation_begin();
    if (likely(nr < NR_syscalls)) {
        nr = array_index_nospec(nr, NR_syscalls);
        regs->ax = sys_call_table[nr](regs);
    }
    instrumentation_end();
    syscall_exit_to_user_mode(regs);
}
#endif

2.4、根据sys_call_table调用对应的功能函数

sys_call_table[nr](regs)
如果我们传入257,就会调用__x64_sys_openat
如果我们传入441,就会调用__x64_sys_get_cpus

2.5、但咱们实际写的函数sys_get_cpus,好像和实际调用函数__x64_sys_get_cpus,差了一个__x64,这需要一个wrapper

arch\x86\include\asm\syscall_wrapper.h
#define SYSCALL_DEFINE0(sname)                      \
    SYSCALL_METADATA(_##sname, 0);                  \
    static long __do_sys_##sname(const struct pt_regs *__unused);   \
    __X64_SYS_STUB0(sname)                      \
    __IA32_SYS_STUB0(sname)                     \
    static long __do_sys_##sname(const struct pt_regs *__unused)

#define __X64_SYS_STUB0(name)                       \
    __SYS_STUB0(x64, sys_##name)

#define __SYS_STUB0(abi, name)                      \
    long __##abi##_##name(const struct pt_regs *regs);      \
    ALLOW_ERROR_INJECTION(__##abi##_##name, ERRNO);         \
    long __##abi##_##name(const struct pt_regs *regs)       \
        __alias(__do_##name);

SYSCALL_DEFINE0(get_cpus),会展开成为
__X64_SYS_STUB0(get_cpus) 
//然后
 __SYS_STUB0(x64, sys_get_cpus)
//然后
long __x64_sys_get_cpus(const struct pt_regs *regs);

这样前后就对上了,glibc和linux内核就通了。

Linux系统调用01

Linux系统调用的整体流程为:
1、应用程序【用户态】通过syscall或glibc进行内核功能调用,这一部分在glibc源码中进行的
2、CPU收到syscall,Linux内核响应syscall调用【内核态】,这一部分在linux源码中进行的
3、返回结果到应用程序【用户态】
本节先处理第一部分:

一、glibc部分
1、应用程序调用open函数

//glibc/intl/loadmsgcat.c
# define open(name, flags)  __open_nocancel (name, flags)

2、展开后实际上调用了

__open_nocancel(name, flags)

3、而__open_nocancel 最终调用了INLINE_SYSCALL_CALL

//glibc/sysdeps/unix/sysv/linux/open_nocancel.c
__open_nocancel(name, flags)
->return INLINE_SYSCALL_CALL (openat, AT_FDCWD, file, oflag, mode);

4、宏展开【理解就好,不保证顺序】

4.1、初始为

INLINE_SYSCALL_CALL (openat, AT_FDCWD, file, oflag, mode);

4.2、第1次展开INLINE_SYSCALL_CALL
[code lang="c"]
#define INLINE_SYSCALL_CALL(...) \
  __INLINE_SYSCALL_DISP (__INLINE_SYSCALL, __VA_ARGS__)

//展开得到:
__INLINE_SYSCALL_DISP(__INLINE_SYSCALL, __VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】)

4.3、第2次展开__INLINE_SYSCALL_DISP
[code lang="c"]
#define __INLINE_SYSCALL_DISP(b,...) \
  __SYSCALL_CONCAT (b,__INLINE_SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)

//展开得到:
__SYSCALL_CONCAT(b【__INLINE_SYSCALL】,__INLINE_SYSCALL_NARGS(__VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】))(__VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】)

4.4、第3次展开__INLINE_SYSCALL_NARGS

__INLINE_SYSCALL_NARGS(__VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】)

#define __INLINE_SYSCALL_NARGS(...) \
  __INLINE_SYSCALL_NARGS_X (__VA_ARGS__,7,6,5,4,3,2,1,0,)

//展开得到:
__INLINE_SYSCALL_NARGS_X(openat, AT_FDCWD, file, oflag, mode,7,6,5,4,3,2,1,0,)

//然后展开__INLINE_SYSCALL_NARGS_X
#define __INLINE_SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n

//展开得到参数个数:
4

//从而4.4的结果为
__SYSCALL_CONCAT(__INLINE_SYSCALL,4)(__VA_ARGS__【openat, AT_FDCWD, file, oflag, mode】)

4.5、然后展开__SYSCALL_CONCAT,其实就是字符拼接

__SYSCALL_CONCAT(__INLINE_SYSCALL,4)

#define __SYSCALL_CONCAT_X(a,b)     a##b
#define __SYSCALL_CONCAT(a,b)       __SYSCALL_CONCAT_X (a, b)

//展开得到:
__INLINE_SYSCALL4

//从而4.5的结果为
__INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode)

4.6、然后展开INTERNAL_SYSCALL4

#define __INLINE_SYSCALL4(name, a1, a2, a3, a4) \
  INLINE_SYSCALL (name, 4, a1, a2, a3, a4)

//展开得到:
INLINE_SYSCALL(openat, 4, AT_FDCWD, file, oflag, mode)

4.7、展开INLINE_SYSCALL

//glibc/sysdeps/unix/sysv/linux/sysdep.h
#define INLINE_SYSCALL(name, nr, args...)       \
  ({                  \
    long int sc_ret = INTERNAL_SYSCALL (name, nr, args);    \
    __glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (sc_ret))    \
    ? SYSCALL_ERROR_LABEL (INTERNAL_SYSCALL_ERRNO (sc_ret))   \
    : sc_ret;               \
  })

//展开得到
INTERNAL_SYSCALL (openat, 4, args【AT_FDCWD, file, oflag, mode】);  

4.8、展开INTERNAL_SYSCALL

#define INTERNAL_SYSCALL(name, nr, args...)       \
  internal_syscall##nr (SYS_ify (name), args)

//展开得到
internal_syscall4(SYS_ify(openat), args【AT_FDCWD, file, oflag, mode】)

//展开 SYS_ify(openat)
#define SYS_ify(syscall_name) __NR_##syscall_name
//得到
__NR_openat

//从而得到
internal_syscall4(__NR_openat, args【AT_FDCWD, file, oflag, mode】)

4.9、最后internal_syscall4中,汇编调用了syscall

glibc\sysdeps\unix\sysv\linux\x86_64\64\arch-syscall.h
#define __NR_openat 257

最终,syscall时,先传入调用号257,然后是四个真正的参数。

Mininet+ONOS网络模拟环境搭建

1、下载mininet虚拟机:

http://mininet.org/download/#option-1-mininet-vm-installation-easy-recommended

2、导入并运行虚拟机

用户名/密码:mininet/mininet

3、安装docker

sudo apt-get update
sudo apt install curl ssh
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

4、拉取onos镜像

sudo docker pull onosproject/onos

5、运行ones容器

#运行
sudo docker run -t -d -p 8181:8181 --name onos1 onosproject/onos

#查看容器运行情况
sudo docker ps

6、连接容器,启用服务

#查看ip地址
sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}' onos1

#ssh连接到容器
ssh -p 8101 karaf@172.17.0.2

#启用OpenFlow Provider Suite
app activate org.onosproject.openflow

#启用 Reactive Forwarding
app activate org.onosproject.fwd

#退出
ctrl+d

7、创建网络

#创建临时网络
sudo mn --topo tree,2 --controller remote,ip=172.17.0.2 --switch=ovsk,protocols=OpenFlow13
*** Creating network
*** Adding controller
Connecting to remote controller at 172.17.0.2:6653
*** Adding hosts:
h1 h2 h3 h4
*** Adding switches:
s1 s2 s3
*** Adding links:
(s1, s2) (s1, s3) (s2, h1) (s2, h2) (s3, h3) (s3, h4)
*** Configuring hosts
h1 h2 h3 h4
*** Starting controller
c0
*** Starting 3 switches
s1 s2 s3 ...
*** Starting CLI:

mininet> nodes
available nodes are:
c0 h1 h2 h3 h4 s1 s2 s3

mininet> links
s1-eth1<->s2-eth3 (OK OK)
s1-eth2<->s3-eth3 (OK OK)
s2-eth1<->h1-eth0 (OK OK)
s2-eth2<->h2-eth0 (OK OK)
s3-eth1<->h3-eth0 (OK OK)
s3-eth2<->h4-eth0 (OK OK)

mininet> net
h1 h1-eth0:s2-eth1
h2 h2-eth0:s2-eth2
h3 h3-eth0:s3-eth1
h4 h4-eth0:s3-eth2
s1 lo: s1-eth1:s2-eth3 s1-eth2:s3-eth3
s2 lo: s2-eth1:h1-eth0 s2-eth2:h2-eth0 s2-eth3:s1-eth1
s3 lo: s3-eth1:h3-eth0 s3-eth2:h4-eth0 s3-eth3:s1-eth2
c0

mininet> h1 ping h2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=43.2 ms

mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 h3 h4
h2 -> h1 h3 h4
h3 -> h1 h2 h4
h4 -> h1 h2 h3
*** Results: 0% dropped (12/12 received)

#退出
ctrl+d

8、网络访问

http://172.17.0.2:8181/onos/ui/login.html
账号/密码:karaf/karaf

然后就可以看到拓扑图了