普通视图
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 update2. 安装基础构建工具 (Host 工具)
编译内核不仅需要交叉编译器,还需要主机端的 gcc、make、bison 等工具。
sudo apt-get install build-essential bison flex libssl-dev lzop u-boot-tools device-tree-compiler3. 安装交叉编译器
安装 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/bin4. 生效配置
source ~/.bashrc验证安装
无论用哪种方法,安装完成后必须验证。
1. 检查版本
arm-linux-gnueabihf-gcc -v2. 预期输出
- 最后一行应显示
gcc version x.x.x ...。 - 如果提示
command not found,说明没安装好或者环境变量没配对。
后缀的区别 (坑点预警)
在下载或安装时,你会看到不同的后缀,千万别选错:
| 后缀名 | 全称 | 含义 | 适用场景 |
|---|---|---|---|
| gnueabihf | Hard Float | 使用硬件 FPU (浮点单元) 进行运算 | AM335x (你的板子), Raspberry Pi 等现代 ARM |
| gnueabi | Soft Float | 使用软件模拟浮点运算 (速度慢) | 非常古老的 ARM9 或无 FPU 的芯片 |
| aarch64 | ARM64 | 64位 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
ficd /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-hpa2. 创建工作目录并赋权 (关键)
默认路径通常在 /var/lib/tftpboot,但为了开发方便,建议修改到用户目录下,并赋予最高权限防止“Permission denied”。
# 1. 创建目录 (建议放在你的工作区)
mkdir -p /home/dq/linux/tftpboot
# 2. 赋予最高权限 (允许任意读写,开发环境图方便)
chmod 777 /home/dq/linux/tftpboot3. 修改配置文件
编辑 /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 restart5. 避坑指南:防火墙 (必做)
TFTP 使用 UDP 69 端口,极易被 Ubuntu 防火墙拦截。开发阶段建议直接关闭,或者放行端口。
# 方案 A: 简单粗暴关闭防火墙 (推荐)
sudo ufw disable
# 方案 B: 仅放行 UDP 69
sudo ufw allow 69/udp6. 本地自测 (验证服务是否存活)
在烧录板子前,先自己连自己测试一下,确保不是 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 命令行设置网络参数及自动化命令:
- 设置网络 IP:
setenv ipaddr 192.168.10.13 # 开发板 IP
setenv serverip 192.168.10.200 # Ubuntu 服务器 IP
saveenv- 定义自动化启动脚本 (
bootcmd):
# 逻辑:从网络下载 dtb 到 0x88000000 -> 下载 zImage 到 0x82000000 -> 启动内核
setenv loadtftp 'tftp 88000000 am335x-bonegreen-gateway.dtb; tftp 82000000 zImage'
setenv bootcmd 'run loadtftp; bootz 82000000 - 88000000'
saveenv- 正常工作时的现象
当一切配置正确且 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_installUbuntu服务端配置
- 安装 NFS 服务
sudo apt-get update
sudo apt-get install nfs-kernel-server- 创建并准备根文件系统目录
假设你的根文件系统(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/- 配置共享权限 (
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:禁用子树检查,提高稳定性。
- 重启并生效服务
# 重新扫描 exports 文件并使配置生效
sudo exportfs -arv
# 重启 NFS 服务
sudo service nfs-kernel-server restart- 避坑指南:网络与防火墙
NFS 依赖多个端口(RPC),如果防火墙开启,挂载必然失败。
# 直接关闭防火墙(开发环境推荐)
sudo ufw disableU-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'
# 启动
bootTFTP无法使用
- 作用:告诉板子“从 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
目标:验证开发板镜像是否正常工作。
- 编写与静态编译
- 问题:动态编译报错
No such file(缺少动态库)。 - 解决:使用
-static静态链接。 - 命令 (在 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- 板上运行
- 命令 (在串口终端):
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
VSCode如何使用自定义模型替代Copilot官方模型并使用Copilot的Agent对话
DeepSeek v4 初体验
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, 100liming1610001python是一门灵活的语言!解决这个问题,可以动态的给某个对象设置成员变量,例如为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__
⚠️ python处理
__双下划线的底层实现,是将该变量替换成了_类名__私有变量名,通过将某个私有变量名修改为_类名__私有变量名的形式,可以突破访问控制,这种访问方式极不推荐
例:
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'}lzj1910002192.4 静态方法
上面例子里面出现在类中,参数以self开头的方法,都是实例方法,在python中,还存在静态方法和类方法
在python中,有一种方法,只是出现在类中,但是不依赖于实例和类的普通方法,叫做静态方法,采用@staticmethod装饰器装饰,通常都是作为一种工具方法,除非传入否则不能访问类和实例的成员,可以使用实例名或类名调用,但是参数列表中不能出现self或cls
例: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还可以实现工厂方法,例如:
class Person: @classmethod def create(cls, name): return cls(**{'name': name}) def __init__(self, name): self.name = nameif __name__ == '__main__': p = Person.create('lsj') print(p.name)2.6 类属性@property
python中,可以定义类属性,类属性是被@property修饰的一种成员,对外表现是变量,对内实现是方法
类属性本身是类的,但是用于实例的属性,可以实现更好的封装
例:
class Stu: @property def age(self): return self.__age def __init__(self, age): self.__age = age passif __name__ == '__main__': print(Stu.age) #<property object at 0x000001D82AA20D10> s1 = Stu(29) print(s1.age) # 29 s2 = Stu(34) print(s2.age) #342.7 其他魔术方法
__call__让实例对象被当作函数调用class Person: def __init__(self, name): self.name = name def __call__(self, age): self.age = age return selfif __name__ == '__main__': p = Person('lsj') q = p(22) print(q.name) #lsj print(q.age) #22
2.8 @dataclass装饰器
@dataclass的用途类似于Java中的Lombok。
@dataclass可以帮我们省略掉__init__(),__eq__(),__repr__()等常见方法的手写代码,专注于业务逻辑,@dataclass中写的变量名:类型的结构会自动转换为实例变量,自动添加到__init__()中。
@dataclass既能声明带默认值的变量,也能生成不带默认值的变量,无默认值的在前,带默认值的在后。
⚠️
@dataclass装饰器会自动生成__init__(),一旦手写了__init__()会导致装饰器失效,所有自动功能全部关闭,如果想用了装饰器又要加自定义的初始化逻辑,用__post_init__(self, ...)避免冲突
例:
from dataclasses import dataclassfrom datetime import date, datetime@dataclassclass Book: # 类变量:所有书籍实例对象共享 publisher = "机械工业出版社" id: int = None bookName: str = None price: float = None author: str = None course_date: date = None start_time: datetime = None # 初始化后校验价格合法性 def __post_init__(self): print('__post_init__') if self.price is not None and self.price < 0: raise ValueError("书籍价格不能为负数!")if __name__ == '__main__': print(Book.publisher) book = Book(id = 1, bookName='疯狂python讲义', price=2.3) print(book) book = Book(id = 1, bookName='疯狂python讲义', price=-2.3)机械工业出版社__post_init__Book(id=1, bookName='疯狂python讲义', price=2.3, author=None, course_date=None, start_time=None)__post_init__Traceback (most recent call last): File "D:\PycharmProjects\python-lang-test\clazz\t3.py", line 27, in <module> book = Book(id = 1, bookName='疯狂python讲义', price=-2.3) ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "<string>", line 9, in __init__ File "D:\PycharmProjects\python-lang-test\clazz\t3.py", line 21, in __post_init__ raise ValueError("书籍价格不能为负数!")ValueError: 书籍价格不能为负数!对__eq__()的重写,也会导致比较对象时,比较的是成员变量的值,而不是地址
if __name__ == '__main__': book1 = Book(id = 1, bookName='疯狂python讲义', price=2.3) book2 = Book(id = 1, bookName='疯狂python讲义', price=2.3) print(book1 == book2) # True book3 = Book(id=1, bookName='疯狂python讲义', price=2.3) book4 = Book(id=1, bookName='疯狂python讲义', price=2.5) print(book3 == book4) # False3.对象初始化
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,...)
例:
class Person: def __new__(cls, name): obj = super().__new__(cls) #调用父类(直到object)的__new__方法开内存空间, print('new') return obj def __init__(self, name): print('init') self.name = nameif __name__ == '__main__': p = Person('liming') print(p.name)Github 桌面端使用经验分享
使用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 -y2.设置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.ymlvim编辑/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 filebeat3.查看索引
登录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
不要因为AI使自己的停止思考!
前端复古风选型必看!像素UI 、8bitUI 、精简复古风UI
前端复古风选型必看!像素UI 、8bitUI 、精简复古风UI。整理了几个亲测还不错的复古风格UI库😂
Chrome浏览器自带翻译的诡异Bug:ID翻译后竟然变化了
当前负责的项目主打海外业务,总免不了和多语言打交道。但最近我在Vite+Vue3+Element Plus技术栈的项目里,遇到了一个堪称“玄学”的bug——Chrome浏览器自带翻译功能,居然能把表格里的数字ID直接改了!从印度同事到国内运...
Ollama本地电脑运行无限制AI模型超简单案例
想在本地用一款 “无拘无束” 的 AI 模型?Ollama 能帮你轻松实现,但先得说清楚:这类 “越狱模型” 虽自由,却可能藏着不少小麻烦。
整理些我常逛的Web3社区
Web3作为热门互联网技术正在快速发展(也包含大量嘎割韭菜的),涌现出了许多有价值的社区。整理了一些我常逛社区资源。
谷歌广告AdSense/GAM 接入避坑 + 填充监听 + 问题排查
作为长期负责海外业务的开发者,公司业务中经常会和谷歌的人员有沟通。在接入谷歌广告生态时,踩了很多坑,很多坑都是和谷歌的技术人员沟通后才发现问题的(有些最新情况和文档不一致,很坑)。
如何优雅判断 AdBlock 是否开启?前端实现方案分享
因为自己主要负责面对海外的用户项目,发现以前的检测AdBlock/AdBlock Plus开启状态方法已失效了,于是专门研究了一下,并尝试了很多方法。发现npm上的插件基本都失效了,于是研究了一下屏蔽原理。
Vite打包zip并改名为md5sum哈希案例
在前端项目 DevOps CICD 流水线中,通过 MD5 哈希值命名打包文件的实践方案。核心流程为:使用工具将 dist 目录打包为 ZIP,通过 md5sum 计算该 ZIP 文件的 MD5 哈希值,再以哈希值重命名 ZIP 文件。
MapLibre Native安卓集成实战:从环境配置到地图展示全指南
MapLibre Native 是一个免费且开源的库,用于在各种平台上的应用程序和桌面应用程序中发布地图。由于 GPU 加速的矢量瓦片渲染,地图的快速显示成为可能。
JavaScript 正则陷阱:全局匹配 /g 导致的 test () 方法异常详解
在 JavaScript 正则表达式使用中,一个看似简单的全局匹配标志/g,可能会在循环检测时引发令人困惑的结果。本文将通过实际案例解析这一现象的底层原因,并提供多种解决方案。
你可能忽略的 HTML 细节:lang 属性的作用与避坑指南
在 HTML 标签中,lang属性看似简单,却隐藏着影响用户体验的关键细节。本文将深入解析lang的作用,结合真实案例说明配置不当可能引发的问题,并提供正确的使用方法。
Vue3中Fragment特性的一个bug,需要留意的注意事项
Vue3中的Fragment特性虽然提供了更灵活的组件结构,但在某些特定场景下可能会出现意外的行为。本文详细介绍了一个需要注意的Fragment相关bug及其解决方案。