阅读视图

U-Boot、内核移植与根文件系统构建(BeagleBone Green Gateway&AM335X)

前言

本文档主要记录了基于 BeagleBone Green Gateway 开发板(核心芯片为 TI AM3358)的 U-Boot、内核移植与根文件系统构建全流程。内容涵盖了在 Ubuntu 下搭建交叉编译环境、编译 Linux 4.19 内核U-Boot、设备树 的详细步骤,在移植过程中遇到的编译器兼容性问题(如 GCC 10+ 报错、架构检测失效)及其修复方法。

一:安装交叉编译工具链

关于 安装 ARM 交叉编译工具链 的教程笔记。

  • 宿主机 (Host): 你的电脑 (Ubuntu x86_64)。
  • 目标机 (Target): 开发板 (AM335x ARMv7)。
  • 交叉编译器: 在 x86 上运行,但生成的是能在 ARM 上运行的可执行代码。

方法 A:使用 APT 包管理器 (最简单,推荐)

这是最快的方法,直接从 Ubuntu 软件源安装。

1. 更新源

sudo apt-get update

2. 安装基础构建工具 (Host 工具)
编译内核不仅需要交叉编译器,还需要主机端的 gcc、make、bison 等工具。

sudo apt-get install build-essential bison flex libssl-dev lzop u-boot-tools device-tree-compiler

3. 安装交叉编译器
安装 Hard Float (hf) 版本的编译器,适配 AM335x 的硬件浮点单元。

# gcc (C编译器) 和 g++ (C++编译器)
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

方法 B:手动安装 Linaro 版本 (指定版本,进阶)

如果你需要特定版本的 GCC (例如为了避开 GCC 10+ 的语法检查严格问题,或者为了匹配旧内核),可以使用此方法。

1. 下载编译器包
前往 Linaro 官网下载 (例如 GCC 7.5):

  • 文件名示例:gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

2. 创建目录并解压
通常安装在 /usr/local/arm 目录下。

sudo mkdir -p /usr/local/arm
# 假设压缩包在当前目录
sudo tar -vxf gcc-linaro-7.5.0-*.tar.xz -C /usr/local/arm/

3. 配置环境变量 (PATH)
为了让终端能在任何地方找到这个命令,需要修改 ~/.bashrc

vim ~/.bashrc

在文件最末尾添加:

# 注意将下面的路径换成你实际解压后的文件夹名
export PATH=$PATH:/usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin

4. 生效配置

source ~/.bashrc

验证安装

无论用哪种方法,安装完成后必须验证。

1. 检查版本

arm-linux-gnueabihf-gcc -v

2. 预期输出

  • 最后一行应显示 gcc version x.x.x ...
  • 如果提示 command not found,说明没安装好或者环境变量没配对。

后缀的区别 (坑点预警)

在下载或安装时,你会看到不同的后缀,千万别选错

后缀名全称含义适用场景
gnueabihfHard Float使用硬件 FPU (浮点单元) 进行运算AM335x (你的板子), Raspberry Pi 等现代 ARM
gnueabiSoft Float使用软件模拟浮点运算 (速度慢)非常古老的 ARM9 或无 FPU 的芯片
aarch64ARM6464位 ARM 架构树莓派4 (64位系统), 手机芯片, RK3399

常用命令别名

为了避免每次编译都要输那一长串前缀,可以在 ~/.bashrc 里加个变量:

# 在 ~/.bashrc 末尾添加
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm

这样以后编译内核只需输:

make zImage  # 甚至不用输 ARCH 和 CROSS_COMPILE,只要 Makefile 支持读取环境变量

二: U-Boot编译和移植

目标:制作一张能引导开发板启动的 SD 卡。

编译 U-Boot

  • 作用:初始化硬件(CPU, DDR),为加载内核做准备。
  • 命令
# 获取
git clone https://github.com/beagleboard/u-boot.git
cd u-boot
git checkout v2021.10-bbb.io-am335x # 切换到对应分支

cd /home/dq/linux/uboot/beagleboard-u-boot

# 清理旧编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
# 配置 (AM335x 通用配置)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- am335x_evm_defconfig
# 编译 (-j4 使用4核加速)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

产物MLO (一级引导), u-boot.img (二级引导)。

  • ROM Code(芯片出厂自带):寻找 MLO 并加载到 SRAM。
  • MLO:初始化 DDR3,寻找 u-boot.img 并搬运到 DDR3。
  • u-boot.img:运行命令行,加载 Kernel(Linux 内核)。
  • Kernel:挂载 Rootfs(根文件系统),启动用户空间。

SD 卡分区与烧录

  • 作用:将 SD 卡分为 Boot 分区 (FAT32) 和 Rootfs 分区 (EXT4)。
  • 命令

sd卡格式化脚本

#! /bin/sh
# mkcard.sh v0.5
# (c) Copyright 2009 Graeme Gregory <dp@xora.org.uk>
# Licensed under terms of GPLv2
#
# Parts of the procudure base on the work of Denys Dmytriyenko
# http://wiki.omap.com/index.php/MMC_Boot_Format

export LC_ALL=C

if [ -z `which bc` ]; then
    echo "no bc binary found"
    exit 1;
fi

if [ $# -ne 1 ]; then
    echo "Usage: $0 <drive>"
    exit 1;
fi

DRIVE=$1

# 清除 SD 卡头部信息
dd if=/dev/zero of=$DRIVE bs=1024 count=1024

# 获取磁盘大小
SIZE=`fdisk -l $DRIVE | grep Disk | grep bytes | awk '{print $5}'`

echo DISK SIZE - $SIZE bytes

# 计算柱面
CYLINDERS=`echo $SIZE/255/63/512 | bc`

echo CYLINDERS - $CYLINDERS

# 使用 sfdisk 进行分区
# 分区1: 63MiB, 类型 0x0C (FAT32 LBA), 可启动 (*)
# 分区2: 4GiB (或剩余空间), 类型默认 (Linux)
sudo sfdisk $DRIVE << EOF
8192,63MiB,0x0C,*
137216,4GiB,,-
EOF

sleep 1

# 格式化分区 1 为 FAT32 (boot)
if [ -b ${DRIVE}1 ]; then
    umount ${DRIVE}1
    mkfs.vfat -F 32 -n "boot" ${DRIVE}1
else
    if [ -b ${DRIVE}p1 ]; then
        umount ${DRIVE}p1
        mkfs.vfat -F 32 -n "boot" ${DRIVE}p1
    else
        echo "Cant find boot partition in /dev"
    fi
fi

# 格式化分区 2 为 ext4 (rootfs)
if [ -b ${DRIVE}2 ]; then
    umount ${DRIVE}2
    mkfs.ext4 -L "rootfs" ${DRIVE}2
else
    if [ -b ${DRIVE}p2 ]; then
        umount ${DRIVE}p2
        mkfs.ext4 -j -L "rootfs" ${DRIVE}p2
    else
        echo "Cant find rootfs partition in /dev"
    fi
fi
cd /home/dq/linux/tool
# 运行脚本格式化 sdb (注意:你的设备号是 sdb)
sudo ./mkcard.sh /dev/sdb

# 挂载并拷贝引导文件
sudo mount /dev/sdb1 /mnt/boot  # (或依赖自动挂载 /media/dq/boot)
sudo cp ../../linux/uboot/beagleboard-u-boot/MLO /mnt/boot/
sudo cp ../../linux/uboot/beagleboard-u-boot/u-boot.img /mnt/boot/

三:Linux 内核编译与部署

目标:编译内核,并解决 U-Boot 网卡驱动不兼容的问题(采用“SD卡存内核 + NFS存文件系统”的混合策略)。

编译内核与设备树

  • 作用:编译操作系统核心 (zImage) 和硬件描述文件 (.dtb)。
  • 命令
# 获取
git clone https://github.com/beagleboard/linux.git --depth 1 -b 4.19.94-ti-r42

cd /home/dq/linux/tool/linux-4.19.94-ti-r42/
# 配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bb.org_defconfig
# 编译内核、设备树和模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage dtbs modules -j4

生成三个部分

  • zImage: 内核镜像。位于 arch/arm/boot/zImage
  • dtbs: 设备树二进制文件。位于 arch/arm/boot/dts/am335x-bonegreen-gateway.dtb
  • modules: 驱动模块。它们被编译成了分布在各个目录下的 .ko 文件。

TFTP 配置

目的:uboot启动后通过tftp拉取镜像zimag和设备树dtb

  • 无需手动拷贝:Ubuntu 编译生成的产物直接放在 TFTP 根目录,开发板重启时自动从网络拉取。
  • 保护 SD 卡:减少对物理存储介质的读写,避免频繁挂载导致的 FAT 分区损坏。
  • 纯内存运行:内核和设备树直接加载到 RAM,不占用 SD 卡空间。

Ubuntu 配置 TFTP

1. 安装服务程序

使用最稳健的 tftpd-hpa 包:

sudo apt-get update
sudo apt-get install tftpd-hpa tftp-hpa

2. 创建工作目录并赋权 (关键)

默认路径通常在 /var/lib/tftpboot,但为了开发方便,建议修改到用户目录下,并赋予最高权限防止“Permission denied”。

# 1. 创建目录 (建议放在你的工作区)
mkdir -p /home/dq/linux/tftpboot

# 2. 赋予最高权限 (允许任意读写,开发环境图方便)
chmod 777 /home/dq/linux/tftpboot

3. 修改配置文件

编辑 /etc/default/tftpd-hpa,将默认配置修改为指向你的新目录。

sudo vim /etc/default/tftpd-hpa

修改后的完整内容:

# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
# 修改 1: 指向你刚才创建的目录
TFTP_DIRECTORY="/home/dq/linux/tftpboot" 
TFTP_ADDRESS=":69"
# 修改 2: 增加 --create 允许上传(可选),保持 --secure
TFTP_OPTIONS="--secure --create"

4. 重启服务并生效

每次修改配置文件后,必须重启服务:

sudo service tftpd-hpa restart

5. 避坑指南:防火墙 (必做)

TFTP 使用 UDP 69 端口,极易被 Ubuntu 防火墙拦截。开发阶段建议直接关闭,或者放行端口。

# 方案 A: 简单粗暴关闭防火墙 (推荐)
sudo ufw disable

# 方案 B: 仅放行 UDP 69
sudo ufw allow 69/udp

6. 本地自测 (验证服务是否存活)

在烧录板子前,先自己连自己测试一下,确保不是 Ubuntu 内部的问题。

# 在 tftpboot 目录下创建一个测试文件
touch /home/dq/linux/tftpboot/test.txt

# 回到家目录尝试下载
cd ~
tftp 127.0.0.1
tftp> get test.txt
tftp> q

# 检查是否下载成功
ls test.txt

如果 ls 能看到文件,说明服务端配置完美!


备选方案 (SD卡部署)

  • 现状:你的 U-Boot 探测不到 PHY 地址 1,网络链路(Link Up)无法建立。
  • 代价:每次修改内核或设备树,必须先进入 Linux,手动挂载 /dev/mmcblk0p1/mnt/sd_boot,然后 cp 覆盖,效率较低
  • 原因:U-Boot 无法通过 TFTP 联网下载,所以直接把内核拷进 SD 卡让它读取。
  • 命令
# 假设 SD 卡 boot 分区挂载在 /media/dq/boot
sudo cp arch/arm/boot/zImage /media/dq/boot/
sudo cp arch/arm/boot/dts/am335x-bonegreen-gateway.dtb /media/dq/boot/
sync  # 同步数据,防止丢失

PS: 后面替换设备树和系统镜像可以通过将sd卡挂载到开发板,然后通过nfs共享文件,将其拷贝到sd卡boot分区


开发板配置

设置产物自动归档
在编译脚本末尾或手动执行,将产物推送到 TFTP 目录(假设为 /home/dq/tftpboot):

cp arch/arm/boot/zImage /home/dq/tftpboot/
cp arch/arm/boot/dts/am335x-bonegreen-gateway.dtb /home/dq/tftpboot/

U-Boot 配置指令 (开发板端)

在 U-Boot 命令行设置网络参数及自动化命令:

  1. 设置网络 IP
setenv ipaddr 192.168.10.13       # 开发板 IP
setenv serverip 192.168.10.200    # Ubuntu 服务器 IP
saveenv
  1. 定义自动化启动脚本 (bootcmd)
# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv
  1. 正常工作时的现象

当一切配置正确且 PHY 地址匹配时,U-Boot 启动会显示:

link up on port 0, speed 100, full duplex
Using ethernet@4a100000 device
TFTP from server 192.168.10.200; our IP address is 192.168.10.13
Filename 'am335x-bonegreen-gateway.dtb'.
Load address: 0x88000000
Loading: ############  (此处会出现大量井号,表示数据高速传输)
done
Bytes transferred = 56234 (dbaa hex)

四:根文件系统构建以及NFS配置

目标:在 Ubuntu 上建立一个文件夹,让开发板通过网线把它当成“硬盘”来挂载,实现文件实时同步,同时加载根文件系统。

# 根文件系统下载
wget -c https://rcn-ee.com/rootfs/eewiki/minfs/debian-10.3-minimal-armel-2020-02-10.tar.xz

安装模块到根文件系统

❯ pwd
/home/dq/linux/nfs/rootfs/lib/modules/4.19.94

# 生成配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bb.org_defconfig

# 编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules -j4

# 安装
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/home/dq/linux/nfs/rootfs/ modules_install

Ubuntu服务端配置

  1. 安装 NFS 服务
sudo apt-get update
sudo apt-get install nfs-kernel-server
  1. 创建并准备根文件系统目录

假设你的根文件系统(Rootfs)存放在 /home/dq/linux/nfs/rootfs

PS: 不同根文件压缩包格式需要不同方式解压

# 1. 创建目录
mkdir -p /home/dq/linux/nfs/rootfs

# 2. 假设你在压缩包所在目录
# -x: 解压, -v: 显示进度, -J: 处理 xz 格式, -f: 指定文件
sudo tar -xvJf debian-10.3-minimal-armel-2020-02-10.tar.xz -C /home/dq/linux/nfs/rootfs/
  1. 配置共享权限 (exports)

NFS 的权限控制通过 /etc/exports 文件管理。

sudo vim /etc/exports

在文件末尾添加以下一行:

/home/dq/linux/nfs/rootfs *(rw,sync,no_root_squash,no_subtree_check)
  • 参数解析
  • rw:可读可写。
  • sync:同步写入(保证数据一致性)。
  • no_root_squash最关键参数。允许板子的 root 用户拥有 Ubuntu 侧对应的 root 权限。如果不加这个,板子无法以 root 身份运行,会导致权限报错。
  • no_subtree_check:禁用子树检查,提高稳定性。
  1. 重启并生效服务
# 重新扫描 exports 文件并使配置生效
sudo exportfs -arv

# 重启 NFS 服务
sudo service nfs-kernel-server restart
  1. 避坑指南:网络与防火墙

NFS 依赖多个端口(RPC),如果防火墙开启,挂载必然失败。

# 直接关闭防火墙(开发环境推荐)
sudo ufw disable

U-Boot设置

直接看第五阶段

五:U-Boot 启动配置

目标:设置 U-Boot 环境变量,启动系统。

TFTP可以使用

即第二阶段tftp开发板的配置

setenv ipaddr 192.168.10.13       # 开发板 IP
setenv serverip 192.168.10.200    # Ubuntu 服务器 IP
saveenv

# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv

# 设置 NFS 挂载参数 (注意 IP 地址)
setenv bootargs 'console=ttyS0,115200n8 root=/dev/nfs nfsroot=192.168.10.200:/home/dq/linux/nfs/rootfs,v3,tcp ip=192.168.10.13:192.168.10.200:192.168.10.100:255.255.255.0::eth0:off'

# 启动
boot

TFTP无法使用

  • 作用:告诉板子“从 SD 卡读内核,去 192.168.10.200 挂载 NFS”。
  • 命令 (在串口终端执行):
# 1. 从 SD 卡读取内核和 DTB
setenv bootcmd 'load mmc 0:1 0x82000000 zImage; load mmc 0:1 0x88000000 am335x-bonegreen-gateway.dtb; bootz 0x82000000 - 0x88000000'

# 2. 设置 NFS 挂载参数 (注意 IP 地址)
setenv bootargs 'console=ttyS0,115200n8 root=/dev/nfs nfsroot=192.168.10.200:/home/dq/linux/nfs/rootfs,v3,tcp ip=192.168.10.13:192.168.10.200:192.168.10.100:255.255.255.0::eth0:off'

# 启动
boot

六:Hello World

目标:验证开发板镜像是否正常工作。

  1. 编写与静态编译
  2. 问题:动态编译报错 No such file (缺少动态库)。
  3. 解决:使用 -static 静态链接。
  4. 命令 (在 Ubuntu 端):
#include <stdio.h>

int main() {
    printf("--------------------------------------\n");
    printf("  Hello, BBGG! This is Gemini speaking. \n");
    printf("  NFS cross-compile works perfectly!   \n");
    printf("--------------------------------------\n");
    return 0;
}
cd /home/dq/linux/nfs/rootfs/home/debian
# 编写代码
sudo vim hello.c 

# 交叉编译 (静态链接)
sudo arm-linux-gnueabihf-gcc -static hello.c -o hello
  1. 板上运行
  2. 命令 (在串口终端):
cd /home/debian
./hello
  • 结果:成功输出 Hello, BBGG!...

补充

在嵌入式 Linux 开发中,解决 GCC 版本冲突(重定义错误)架构检测失败(ISB 指令错误) 是适配旧内核(如 4.19)的关键步骤。以下是针对这两处修改的笔记整理:


1. 解决主机工具编译冲突 (yylloc 多重定义)

背景:当 Ubuntu 宿主机的 GCC 版本 10 时,默认开启 -fno-common,导致内核自带的 dtc(设备树编译器)工具在链接时报错。

  • 修改文件:顶层 Makefile
  • 具体位置:搜索 KBUILD_HOSTCFLAGS 定义处(通常在 400 行左右)。
  • 修改内容
    在变量末尾手动添加 -fcommon 参数。
# 原代码:
KBUILD_HOSTCFLAGS   := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
                       -fomit-frame-pointer -std=gnu89 $(HOST_LFS_CFLAGS) \
                       $(HOSTCFLAGS)

# 修改后:
KBUILD_HOSTCFLAGS   := -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 \
                       -fomit-frame-pointer -std=gnu89 -fcommon $(HOST_LFS_CFLAGS) \
                       $(HOSTCFLAGS)

2. 解决架构降级导致的汇编错误 (isb 指令不支持)

背景:内核通过 cc-option 测试编译器。在使用硬浮点(Hard-Float)交叉编译器时,由于测试命令缺少 FPU 参数导致测试失败,内核 Makefile 会误认为编译器不支持 ARMv7,从而自动降级到 ARMv5,导致不识别 v7 指令 isb

  • 修改文件arch/arm/Makefile
  • 具体位置:搜索 CONFIG_CPU_32v7 所在行(通常在 65 行左右)。
  • 修改内容
    废除自动检测逻辑,直接硬编码(Hardcode)指定架构为 -march=armv7-a
# 原代码:
arch-$(CONFIG_CPU_32v7)  := -D__LINUX_ARM_ARCH__=7 $(call cc-option,-march=armv7-a,-march=armv5t -Wa$(comma)-march=armv7-a)

# 修改后(删除检测函数,直接指定架构):
arch-$(CONFIG_CPU_32v7)  := -D__LINUX_ARM_ARCH__=7 -march=armv7-a
  •  

Python中的类和对象

未完待续

1.定义类

python3中的根类,有且只有一个:object(小写)

⚠️大写的Object不存在于python内置中,是自己定义的,为防止混淆不建议使用

__bases__是python中一个内置的变量,代表基类

# 隐式继承,python3自动处理class Cat:    pass# 显式继承class Dog(object):    passif __name__ == '__main__':    print( Cat.__bases__ ) # (<class 'object'>,)    print( Dog.__bases__ ) # (<class 'object'>,)

2.类的成员

2.1 构造方法

  • python初始化类的对象,使用__init__()这个特殊方法作为类的构造方法,创建对象时会被自动调用。
  • python构造方法不支持重载,不能弄多个__init__(),否则最后一个覆盖前面的。

构造方法就是一种实例方法,实例方法第一个参数必须是self代表当前对象

class Cat:    def __init__(self):        print('init')        print(self)        print(self.__class__)if __name__ == '__main__':    cat = Cat()    print(cat)
init<__main__.Cat object at 0x000001982BE1D400><class '__main__.Cat'><__main__.Cat object at 0x000001982BE1D400>

2.2 类和实例的属性

  • 实例属性在构造器__init__中声明,__init__()中通过self.定义实例属性
  • 定义在类以内,__init__()以外的属性是类属性,类似Java中的static
  • 实例属性和类属性不同名时,实例属性通过对象.访问,类属性可以通过类.访问或对象.访问
  • 实例属性和类属性同名是合法但是不推荐的,这种情况下,实例和类会各自有一份,对象.访问到的是实例属性,类属性只能通过类.访问

⚠️__init__()外面的同名属性前面没有static且和self.后面的属性名重名时,self.极易被Java程序员误判为是在为外面的属性初始化或赋值,这种Java的思路,在python中是错误的

例:

  • __init__()里面的重名name,age是实例属性,通过对象.访问
  • __init__()外面的重名name,age是类属性,通过类.访问
  • country属性不重名,可通过对象.访问,也可以通过类.访问
class Stu:    name = 'simaple_name'    age = 0    country = 'China'    def __init__(self, age, name):        print('init')        self.age = age        self.name = name    def speak(self):        print(self.age, self.name)if __name__ == '__main__':    stu1 = Stu(18, '元宝')    stu2 = Stu(20, '大黄')    stu1.speak()    stu2.speak()    print('---------------')    print(Stu.name)    print(Stu.age)    print('-------------')    print(stu1.country)    print(stu2.country)    print(Stu.country)
initinit18 元宝20 大黄---------------simaple_name0-------------ChinaChinaChina

有时候,一个类存在属性过多,逐一用参数赋值会很复杂,例如:

  • def __str__(self)同样是一个内置函数,类似Java中的toString(),打印对象时转换为字符串
  • 双下划线__开头的属性,属于私有属性,类外不可直接访问
class Stu:    def __init__(self, name, age, no, score):        self.name = name        self.age = age        self.no = no        self.__score = score    def __str__(self):        return f'{self.name}, {self.age}, {self.no}, {self.__score}'if __name__ == '__main__':    stu1 = Stu('liming', 16, 10001, 100)    print(stu1)    print(stu1.name)    print(stu1.age)    print(stu1.no)    #print(stu1.__score) ❌错误 AttributeError: 'Stu' object has no attribute '__score'
liming, 16, 10001, 100liming1610001

python是一门灵活的语言!解决这个问题,可以动态的给某个对象设置属性,例如为stu2动态指定一个属性no

self.__dict__可以代表对象所有属性

class Stu:    def __init__(self, name, age, score):        self.name = name        self.age = age        self.__score = score    def __str__(self):        return f'{self.__dict__}'if __name__ == '__main__':    stu1 = Stu('liming', 16, 100)    print(stu1)    stu2 = Stu('liming', 16, 100)    stu2.no = '10002'    print(stu2)
{'name': 'liming', 'age': 16, '_Stu__score': 100}{'name': 'liming', 'age': 16, '_Stu__score': 100, 'no': '10002'}

上面的写法不推荐,可以采用定义好再去赋值的写法

class Stu:    def __init__(self):        self.name = None        self.age = None        self.no = None    def __str__(self):        return f'{self.__dict__}'if __name__ == '__main__':    stu1 = Stu()    print(stu1)    stu2 = Stu()    stu2.age = 12    stu2.no = '123'    stu2.name = 'sxh'    print(stu2)
{'name': None, 'age': None, 'no': None}{'name': 'sxh', 'age': 12, 'no': '123'}

还可以通过可变参数

class Stu:    def __init__(self, **kwargs):        self.name = kwargs.get('name', None)        self.age = kwargs.get('age', None)        self.no = kwargs.get('no', None)    def __str__(self):        return f'{self.__dict__}'if __name__ == '__main__':    stu1 = Stu(name='lzj', age=19, no='10002')    print(stu1)    stu2 = Stu(name='xiaohong', age=19 )    print(stu2)
{'name': 'lzj', 'age': 19, 'no': '10002'}{'name': 'xiaohong', 'age': 19, 'no': None}

2.3 成员访问限制

在python中,属性可以进行访问权限控制,和Java类似,如果要访问受保护的属性,就定义方法,这是类封装性的体现

  • 受保护的属性 _单个下划线开头,仅仅起到提醒作用,但是不阻止访问,例如_age
  • 私有属性 __双下划线开头,类外不可访问,否则程序运行出错,例如__age
  • 公开访问属性 没有下划线的,无限制
  • 魔法属性 是python内置的,有特殊的含义,不可自行定义,例如__dict__

⚠️ __双下划线不可访问的底层实现,实际上是将该变量替换成了_类名__私有变量名,通过将某个私有变量名修改为_类名__私有变量名的形式,可以突破访问控制,这种访问方式极不推荐

例:

class Stu:    def __init__(self, **kwargs):        self.name = kwargs.get('name', None)        self.__age = kwargs.get('age', None)        self._no = kwargs.get('no', None)    def __str__(self):        return f'{self.__dict__}'    def getAge(self):        return self.__ageif __name__ == '__main__':    stu1 = Stu(name='lzj', age=19, no='10002')    print(stu1)    print(stu1.name)    print(stu1.getAge())    print(stu1._no) #不建议的    print(stu1._Stu__age) #不建议的    #print(stu1.__age) #运行出错
{'name': 'lzj', '_Stu__age': 19, '_no': '10002'}lzj191000219

2.4 静态方法

上面例子里面出现在类中,参数以self开头的方法,都是实例方法,在python中,还存在静态方法和类方法

在python中,有一种方法,只是出现在类中,但是不依赖于实例和类的普通方法,叫做静态方法,采用@staticmethod装饰器装饰,通常都是作为一种工具方法,除非传入否则不能访问类和实例的成员,可以使用实例名或类名调用,但是参数列表中不能出现selfcls

例:def sum(a, b)就是静态方法

class Stu:    def __init__(self):        name = None    def call(self):        self.sum(1, 2)        self.__class__.sum(3, 4)        Stu.sum(5, 6)    @staticmethod    def sum(a, b):        return a + bif __name__ == '__main__':    stu1 = Stu()    stu1.call()    stu1.sum(7, 8)    Stu.sum(9, 10)

2.5 类方法

类方法类似Java中的static方法,python的类方法声明时第一个形参永远是cls代表类本身(不是实例),同时除__new__()以外,所有类方法都要采用@classmethod函数装饰器修饰。

python的类方法既能通过类也能通过实例调用,但是为防混淆不建议用实例调用。

主要用于操作类属性或实现工厂模式。

可直接访问类属性,类方法和静态方法,不能直接操作实例属性或实例方法。

例:

class Stu:    school = 'bupt'    def __init__(self):        name = None    def call(self):        pass    @classmethod    def classfunc1(cls):        print(cls.__name__)        print(cls.school)        cls.sum(1, 2)        cls.classfunc2()    @classmethod    def classfunc2(cls):        print('classfunc2')    @staticmethod    def sum(a, b):        return a + bif __name__ == '__main__':    Stu.classfunc1()    s1 = Stu()    s1.classfunc2()    s1.__class__.classfunc2()
Stubuptclassfunc2classfunc2classfunc2

3.对象初始化

python对象初始化o = Obj(),会经历以下几个过程:

1.调用该Obj类的__new__(cls,...)方法,在内存中开辟空间,创建一个空的实例对象
2.__new__(cls,...)执行完成,必须返回一个当前Obj类的实例对象,就是后续的self
3.python自动把返回的对象,传给__init__(self,...)的第一个参数self
4.调用__init__(self,...)给这个空对象绑定属性,初始化数据
5.返回初始化完成的实例对象给变量o

其中:

  • __new__(cls,...)是类方法,但是无需显式添加@classmethod装饰器
  • __new__(cls,...)必须有返回值,返回当前类对象时执行__init__(self,...),返回None则跳过__init__(self,...)
  • __new__(cls,...)的时机一定早于__init__(self,...),先创建再初始化
  • 永远不要出现多个生效的__init__(self,...)
  •  

使用Filebeat采集Nginx日志到ES

filebeat是传统elk组件中logstach的升级替代,能够高性能的采集一些中间件的日志到es中,供检索分析。

1.安装filebeat

首先要安装filebeat到nginx所在服务器,因为我的服务器是rocky linux属于redhat系,故这里通过yum安装,先设置安装源

导入GPG-KEY

rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch

新建一个elastic.repo文件在/etc/yum.repos.d下,并粘贴安装源地址

vim /etc/yum.repos.d/elastic.repo

[elastic-9.x]name=Elastic repository for 9.x packagesbaseurl=https://artifacts.elastic.co/packages/9.x/yumgpgcheck=1gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearchenabled=1autorefresh=1type=rpm-md

接下来执行安装,直到安装完成

yum install filebeat -y

2.设置nginx和filebeat

首先确认nginx的日志路径和日志格式,一般日志路径默认就是:

  • /var/log/nginx/access.log 常规访问日志
  • /var/log/nginx/error.log 错误日志

在nginx.conf配置文件中,默认的日志格式是:

log_format  main  ' $remote_addr - $remote_user [$time_local] "$request" '                  '$status $body_bytes_sent "$http_referer" '                  '"$http_user_agent" "$http_x_forwarded_for"';

为了区分各个主机的访问记录,我选择增加一个主机的字段:$host

log_format  main  '$host $remote_addr - $remote_user [$time_local] "$request" '                  '$status $body_bytes_sent "$http_referer" '                  '"$http_user_agent" "$http_x_forwarded_for"';

亲测filebeat可以识别上述的日志格式,自动提取有效信息

然后设置filebeat,通过yum安装的filebeat,默认全局配置文件位于/etc/filebeat/filebeat.yml,有这样几项需要修改

output.elasticsearch:  # 改成自己es地址和端口  hosts: ["localhost:9016"]  # 改成自己的索引格式  index: "nginx-logs-%{+yyyy.MM.dd}"  # 通信协议按需要修改  protocol: "http"  # es用户名密码,必须设置  username: "elastic"  password: "***************"# 需要新增这两项,索引数据格式模板名称setup.template.name: "nginx-logs"setup.template.pattern: "nginx-logs-*"

然后对nginx的采集功能进行设置,filebeat支持很多中间件的日志采集,通过yum安装的filebeat,默认的各中间件的采集配置文件位于:/etc/filebeat/modules.d/

首先要将默认的nginx配置文件nginx.yml.disabled复制出一份nginx.yml,因为最后filebeat只会自动导入读取.yml结尾的文件

cp /etc/filebeat/modules.d/nginx.yml.disabled /etc/filebeat/modules.d/nginx.yml

vim编辑/etc/filebeat/modules.d/nginx.yml配置文件,针对nginx的采集进行配置

- module: nginx  # 打开常规访问日志采集,指定日志路径  access:    enabled: true    var.paths: ["/var/log/nginx/access.log"]    var.timezone: "Asia/Shanghai"  # 打开错误日志采集,指定日志路径  error:    enabled: true    var.paths: ["/var/log/nginx/error.log"]    var.timezone: "Asia/Shanghai"

都修改完成后,通过filebeat test config命令,验证配置文件是否有语法错误

[root@VM-0-3-rockylinux ~]# filebeat test configConfig OK

然后启动filebeat,并且能看到进程,启动成功

[root@VM-0-3-rockylinux ~]# systemctl start filebeat[root@VM-0-3-rockylinux ~]# ps -ef | grep filebeatroot      279214       1  0 Apr17 ?        00:00:09 /usr/share/filebeat/bin/filebeat --environment systemd -c /etc/filebeat/filebeat.yml --path.home /usr/share/filebeat --path.config /etc/filebeat --path.data /var/lib/filebeat --path.logs /var/log/filebeatroot      484905  454652  0 14:33 pts/2    00:00:00 grep --color=auto filebeat

3.查看索引

登录kibana,打开开发工具,就能看到filebeat建的索引和采集到的日志了,还可以根据业务需要制作图表等

还可以通过检索,通过链接和访问次数进行聚合,查出一些攻击和刺探的恶意请求,例如:

GET /nginx-logs-2026.04.18/_search{  "size": 0,   "aggs": {    "domain_counts": {      "terms": {        "field": "url.domain",       "size": 20000      },      "aggs": {        "domains_per_ip": {          "terms": {            "field": "source.ip",            "size": 20000                      },          "aggs": {            "domains_per_path": {              "terms": {                "field": "url.path",                "size": 20000                              }            }          }        }      }    }  }}
  •  

Hello Halo

如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo 进行创作,希望能够使用愉快。
  •  

Chrome浏览器自带翻译的诡异Bug:ID翻译后竟然变化了

当前负责的项目主打海外业务,总免不了和多语言打交道。但最近我在Vite+Vue3+Element Plus技术栈的项目里,遇到了一个堪称“玄学”的bug——Chrome浏览器自带翻译功能,居然能把表格里的数字ID直接改了!从印度同事到国内运...

  •