[{"content":" 小米 Mini 刷 OpenWrt 24.10 使用品胜 MA156 (中兴微) 4G 网卡 手头有一台吃灰多年的小米路由器 Mini，想着把它利用起来做一个 4G 上网热点。经历了一番折腾，从刷机到驱动网卡，特别是解决**品胜 MA156（中兴微芯片）**被识别成 U 盘的问题，积累了一些经验。\n本文记录了我完整的排查和解决过程，希望能帮到同样遇到“插上 4G 棒子没反应”的朋友。\n1. 环境准备 首先把小米 Mini 刷成了最新的 OpenWrt 24.10.0 版本。\n设备：小米路由器 Mini (MT7620) 系统：OpenWrt 24.10.0 (Linux 6.6.73) 2. 安装 USB 基础驱动包 刷完机第一件事，就是安装 USB 支持包。因为不知道网卡具体用什么协议，所以我把常见的 USB 网络驱动都装上了，确保万无一失。\nSSH 登录路由器，执行安装：\nopkg update opkg list | grep usb # 查看当前包（这是我安装好的列表） 核心安装包清单：\nkmod-usb-core, kmod-usb2 (基础 USB 支持) usb-modeswitch (关键！用于模式切换) kmod-usb-net, kmod-usb-net-cdc-ether, kmod-usb-net-rndis (网卡协议驱动) 3. 测试：中兴网卡 (正常) 为了确认路由器的 USB 口和基础驱动没问题，我先插了一个老款的中兴 USB 上网卡。\n结果：插上后，系统日志直接识别出 LTE 设备，在“网络 -\u0026gt; 接口”里直接能看到 usb0 网卡。 结论：说明 OpenWrt 24.10 的 USB 功能和基础驱动是正常的。 4. 问题出现：品胜 MA156 (中兴微) 罢工 拔掉华为，插上这次的主角——品胜 MA156（拆机确认是中兴微 ZXIC 方案）。\n现象：路由器没有任何反应，在网络接口里找不到新网卡。 初步判断：驱动没挂载上，或者设备模式不对。 5. 深入排查：寻找“失踪”的网卡 为了搞清楚发生了什么，我在终端输入了查看 USB 状态的命令：\ncat /sys/kernel/debug/usb/devices 获取到的关键信息（节选）：\nT: Bus=01 Lev=01 ... Dev#= 3 Spd=480 MxCh= 0 P: Vendor=19d2 ProdID=0557 Rev= 1.01 S: Manufacturer=ALK,Incorporated S: Product=ALK Mobile Boardband C:* #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr=500mA I:* If#= 0 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=06 Prot=50 Driver=(none) 🔴 问题找到了：\nID：19d2:0557。 Cls=08(stor.)：这是最明显的证据。08 代表 Mass Storage，也就是说 OpenWrt 把它当成了一个 U 盘/光驱。 Driver=(none)：因为是 U 盘模式，但又没挂载存储驱动，所以这里是空的。 这就是所谓的 ZeroCD 模式：网卡为了兼容 Windows 驱动安装，默认伪装成光驱。我们需要做的是让它“变身”。\n6. 解决：修改 usb-mode.json 既然知道它是中兴微的芯片，且处于存储模式，就需要配置 usb-modeswitch 来触发它切换。\nOpenWrt 24.10 使用 /etc/usb-mode.json 来管理设备库。我查阅资料后发现，中兴微的设备通常支持 StandardEject（标准弹出）模式——只要模拟弹出光驱，它就会自动切换成网卡。\n操作步骤： 编辑配置文件 vi /etc/usb-mode.json，加入了以下针对 19d2:0557 的配置：\n\u0026#34;19d2:0557\u0026#34;: { \u0026#34;*\u0026#34;: { \u0026#34;mode\u0026#34;: \u0026#34;StandardEject\u0026#34;, \u0026#34;msg\u0026#34;: [ ] } } (注意：这里利用了 OpenWrt 自带的 StandardEject 机制，比填一大串 16 进制代码更稳)\n7. 修改前后的对比 修改保存后，我拔掉网卡重新插入，让 usbmode 重新扫描。\n再次运行 lsusb -v 和 cat /sys/kernel/debug/usb/devices，我们可以看到明显的差异对比：\n对比项 修改前 (未识别) 🔴 修改后 (成功识别) 🟢 设备 ID 19d2:0557 19d2:0558 (ID 变了！) 设备类型 Cls=08(stor.) (U 盘) Cls=02(comm.) (通信设备) 识别描述 Mass Storage CDC Ethernet Control Model (ECM) 驱动程序 Driver=(none) Driver=cdc_ether 网络接口 无 出现 eth1 系统日志证实了切换成功：\nBus 001 Device 003: ID 19d2:0558 ALK,Incorporated ALK Mobile Boardband bInterfaceClass 2 [unknown] iInterface 6 CDC Ethernet Control Model (ECM) 系统识别出了 CDC Ethernet 协议，并加载了对应的驱动。\n8. 最后一步：Web 端添加接口 既然底层已经认出了 eth1，剩下的就在 LuCI 网页端点点鼠标了。\n登录 OpenWrt 后台，进入 网络 -\u0026gt; 接口。 点击 添加新接口。 名称：随便填，比如 LTE。 协议：选 DHCP 客户端（因为 UFI 棒子自带路由功能）。 设备：在下拉列表中选择 Ethernet Adapter: \u0026ldquo;eth1\u0026rdquo; (也就是刚才识别出来的 usb0)。 保存并应用。 稍等几秒，可以看到 IP 地址已经获取到了，网络连接成功！\n","permalink":"http://blog.cookbyte.net/post/openwrt%E4%BD%BF%E7%94%A8%E4%B8%AD%E5%85%B4%E5%BE%AEufi/","summary":"小米 Mini 刷 OpenWrt 24.10 使用品胜 MA156 (中兴微) 4G 网卡 手头有一台吃灰多年的小米路由器 Mini，想着把它利用起来做一个 4G 上网热点。经历了一番折腾，从刷机到驱动","title":"小米 Mini 刷 OpenWrt 24.10 使用品胜 MA156 (中兴微) 4G 网卡"},{"content":" github 三种不同的 token GITHUB_TOKEN 该 token 为用于 GitHub actions 的内置 token, 运行 actions 自动生成，可用于对执行 actions 的仓库进行访问操作，直接使用 secrets.GITHUB_TOKEN 即可在 GitHub actions 中使用。\nGitHub Actions 使用方式：\n- name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} # 另外还支持 deploy_key 和 personal access token (https://github.com/peaceiris/actions-gh-pages#readme) deploy_key 该 key 针对仓库，是公钥加私钥形式，可使用 SSH key, 配置方式如下\n生成 SSH Key 打开 terminal 输入下面的命令生成 id_rsa 和 id_rsa.pub 文件： ssh-keygen -t rsa -C me@xxx.com 其中 me@xxx.com 就是 GitHub 账号的邮箱。 2. 填写 Deploy Keys 和 Secrets\n打开源码仓库，在设置中找到「Secrets」\n第 1/3 步：添加 DEPLOY_KEY 内容是 id_rsa 文件的全部内容。\n第 2/3 步：添加 EMAIL 内容是 GitHub 邮箱。\n第 3/3 步：添加 NAME 内容是 GitHub 账号名。\n打开 deploy 目标仓库，在设置中找到「Deploy Keys」\n第 1/1 步：添加 deploy_key.pub 内容是 id_rsa.pub 文件的全部内容。\nGitHub Actions 使用方式：\n- name: Deploy uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.DEPLOY_KEY }} # 另外还支持 github_token 和 personal_token (https://github.com/peaceiris/actions-gh-pages#readme) personal access token token 的生成需要到这里：个人头像 -\u0026gt; Settings -\u0026gt; Developer settings -\u0026gt; Personal access tokens，点击 Generate new token。这一步需要输入密码，然后可以根据需要定制 token 的权限\n该 token 的权限最大，可对账户进行操作，在生成时可以选在 token 的权限范围，越小越好。\nGitHub Actions 使用方式 (将生成的 token 添加到对应仓库的 secret 中):\n- name: Deploy uses: peaceiris/actions-gh-pages@v3 with: personal_token: ${{ secrets.PERSONAL_ACCESS_KEY_TO_DEPLOY_BLOG }} # 另外还支持 github_token 和 deploy_key (https://github.com/peaceiris/actions-gh-pages#readme) ","permalink":"http://blog.cookbyte.net/post/github-token/","summary":"github 三种不同的 token GITHUB_TOKEN 该 token 为用于 GitHub actions 的内置 token, 运行 actions 自动生成，可用于对执行 actions 的仓库进行访问操作，直接使用 secrets.GITHUB_TOKEN 即可在 GitHub actions 中使用。 GitHub Actions 使用方式： - name: Deploy uses: peaceiris/actions-gh-pages@v3","title":"GitHub 不同 token 的区别和用法"},{"content":" gcc4.8.5 源码下载 wget ftp://ftp.gnu.org/gnu/gcc/gcc-4.8.5/gcc-4.8.5.tar.gz tar xzf gcc-4.8.5.tar.gz 安装静态库 yum install libstdc++-static 下载依赖 cd gcc-4.8.5 ./contrib/download_prerequisites 编译 mkdir build cd build # 生成makefile, 这些参数可以用已有的gcc参数 gcc -v 可用看到, 也可以自己修改需要的参数 # prefix指定安装路径 ../configure --prefix=/opt/gcc4.8.5 --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++ --enable-plugin --enable-initfini-array --disable-libgcj --enable-gnu-indirect-function --with-tune=generic --disable-multilib --with-arch_32=x86-64 --build=x86_64-linux-gnu # 16线程编译, 编译过程耗时较长 make -j16 # 安装 make install 错误解决 错误1 In file included from ../../gcc/cp/except.c:1008: cfns.gperf:101:1: error: ‘const char* libc_name_p(const char*, unsigned int)’ redeclared inline with ‘gnu_inline’ attribute cfns.gperf:26:14: note: ‘const char* libc_name_p(const char*, unsigned int)’ previously declared here cfns.gperf:26:14: warning: inline function ‘const char* libc_name_p(const char*, unsigned int)’ used but never defined make[3]: *** [Makefile:1059: cp/except.o] Error 1 make[3]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build/gcc\u0026#39; make[2]: *** [Makefile:4163: all-stage1-gcc] Error 2 make[2]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build\u0026#39; make[1]: *** [Makefile:20822: stage1-bubble] Error 2 make[1]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build\u0026#39; make: *** [Makefile:892: all] Error 2 patch地址\n错误2 In file included from ../../../libgcc/unwind-dw2.c:405:0: ./md-unwind-support.h: In function ‘x86_64_fallback_frame_state’: ./md-unwind-support.h:65:47: error: dereferencing pointer to incomplete type sc = (struct sigcontext *) (void *) \u0026amp;uc_-\u0026gt;uc_mcontext; ^ make[3]: *** [../../../libgcc/shared-object.mk:14: unwind-dw2.o] Error 1 make[3]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build/x86_64-linux-gnu/libgcc\u0026#39; make[2]: *** [Makefile:16943: all-stage1-target-libgcc] Error 2 make[2]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build\u0026#39; make[1]: *** [Makefile:20822: stage1-bubble] Error 2 make[1]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build\u0026#39; make: *** [Makefile:892: all] Error 2 patch地址\n错误3 ../../../../libsanitizer/asan/asan_linux.cc: In function ‘bool __asan::AsanInterceptsSignal(int)’: ../../../../libsanitizer/asan/asan_linux.cc:95:20: error: ‘SIGSEGV’ was not declared in this scope return signum == SIGSEGV \u0026amp;\u0026amp; flags()-\u0026gt;handle_segv; ^ ../../../../libsanitizer/asan/asan_linux.cc:96:1: warning: control reaches end of non-void function [-Wreturn-type] } ^ make[4]: *** [Makefile:441: asan_linux.lo] Error 1 make[4]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build/x86_64-linux-gnu/libsanitizer/asan\u0026#39; make[3]: *** [Makefile:326: all-recursive] Error 1 make[3]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build/x86_64-linux-gnu/libsanitizer\u0026#39; make[2]: *** [Makefile:15546: all-stage1-target-libsanitizer] Error 2 make[2]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build\u0026#39; make[1]: *** [Makefile:20822: stage1-bubble] Error 2 make[1]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build\u0026#39; make: *** [Makefile:892: all] Error 2 patch地址\n错误4 ../../../../libsanitizer/tsan/tsan_platform_linux.cc: In function ‘int __tsan::ExtractResolvFDs(void*, int*, int)’: ../../../../libsanitizer/tsan/tsan_platform_linux.cc:295:16: error: ‘statp’ was not declared in this scope __res_state *statp = (__res_state*)state; ^ ../../../../libsanitizer/tsan/tsan_platform_linux.cc:295:37: error: expected primary-expression before ‘)’ token __res_state *statp = (__res_state*)state; ^ ../../../../libsanitizer/tsan/tsan_platform_linux.cc:295:38: error: expected ‘;’ before ‘state’ __res_state *statp = (__res_state*)state; ^ make[4]: *** [Makefile:475: tsan_platform_linux.lo] Error 1 make[4]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build/x86_64-linux-gnu/libsanitizer/tsan\u0026#39; make[3]: *** [Makefile:326: all-recursive] Error 1 make[3]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build/x86_64-linux-gnu/libsanitizer\u0026#39; make[2]: *** [Makefile:15546: all-stage1-target-libsanitizer] Error 2 make[2]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build\u0026#39; make[1]: *** [Makefile:20822: stage1-bubble] Error 2 make[1]: Leaving directory \u0026#39;/root/gcc-4.8.5-fixed/build\u0026#39; make: *** [Makefile:892: all] Error 2 patch地址\nARM平台编译问题 arm平台上编译会因为依赖版本低, 导致编译失败\n- MPFR=mpfr-2.4.2 - GMP=gmp-4.3.2 - MPC=mpc-0.8.1 + MPFR=mpfr-4.1.0 + GMP=gmp-6.2.1 + MPC=mpc-1.2.1 修改contrib/download_prerequisites 文件, 升级依赖版本即可\ngcc版本共存环境变量配置 切换gcc版本到gcc4.8.5 关键配置 PATH , 配置gcc命令的路径, 若指定gcc版本, 需将gcc路径配置在系统原有PATH之前 # gcc GCCDIR=/opt/gcc4.8.5 # path PATH=$GCCDIR/bin:$PATH export PATH LIBRARY_PATH , 该环境变量配置程序编译时库查找路径 LIBRARY_PATH=$GCCDIR/lib64:$LIBRARY_PATH export LIBRARY_PATH LD_LIBRARY_PATH, 该环境变量配置程序运行时库查找路径 LD_LIBRARY_PATH=$GCCDIR/lib64:$LD_LIBRARY_PA export LD_LIBRARY_PATH 提供一份修改好的代码 gcc-4.8.5-fixed\n","permalink":"http://blog.cookbyte.net/post/compile-gcc-on-linux/","summary":"gcc4.8.5 源码下载 wget ftp://ftp.gnu.org/gnu/gcc/gcc-4.8.5/gcc-4.8.5.tar.gz tar xzf gcc-4.8.5.tar.gz 安装静态库 yum install libstdc++-static 下载依赖 cd gcc-4.8.5 ./contrib/download_prerequisites 编译 mkdir build cd build # 生成makefile, 这些参数可以用已有的gcc参数 gcc -v 可用看到, 也可以自己","title":"linux降低gcc版本, 高版本gcc(gcc10.3.1)下编译低版本gcc(gcc4.8.5), 设置gcc版本共存"},{"content":" 问题背景 公司新产品需要高性能业务平台, 需要对现有的平台进行性能优化, 进行一系列优化后, 性能提升50%左右, 但是做性能测试时发现, 进程存在使用 new/malloc 分配内存时失败的情况, 导致进程coredump, 无法完成稳定性行测试, 于是通过 valgrind 和 strace 等工具分析定位高负载情况下, 进程分配内存失败问题.\n问题分析过程 review优化代码 在做性能测试时, 数据是通过将量打到本机cpu消耗70%时的数据作为对比数据, 使用优化后的版本进行70%cpu压测时, 跑一段时间后, 进程会报告内存分配失败问题. 遇到该问题时首先想到, 可能是优化部分代码有问题, 毕竟做了多线程优化, 可能是有数据竞争问题, 导致跑一段时间内存被破坏了, 这么怀疑的原因是, 使用优化前版本跑70%cpu压测稳定24小时不会出现此问题 , 于是乎, 对代码进行review, 翻来覆去看了几遍, 没发现有异常地方.\n降低gcc版本 进行过代码review之后, 没有发现有问题的地方. 于是想到另一个方面, 本次优化还有一个变化是迁移了系统, 从centos迁移到了openEuler系统, openEuler系统的gcc(gcc10)版本比之前使用的 gcc(4.8.5)高出很多, 由于平台代码历史有二十多年, 所以使用的还是gnu++98 标准写的, 其中的有些语法在高版本上编译不过, 需要修改, 还有很多写法不规范的地方, 在gcc10开启 -O2 优化下, 进程会重启, 例如非 void 函数没写返回值等. 所以怀疑是gcc版本太高, 代码里不规范的地方太多, 没有修改完, 所以运行会有问题, 因此在openEuler系统上编译了gcc4.8.5, 使用低版本gcc进行代码编译, 废了一番劲编译好gcc之后, 使用低版本gcc编译平台代码, 进行性能测试, 发现还是会出现内存分配失败的情况, 这么看来, 该问题和gcc版本没有关系.\n分析core文件 尝试过回退gcc版本后问题依旧, 于是还是来分析core文件, 分析core文件发现几个现象:\n出core的地方不同 几乎都是关于内存分配的地方 core一例 [Current thread is 1 (Thread 0x7f0d967fc640 (LWP 55019))] (gdb) bt #0 0x00007f0dfc9951be in TemplateClass::TemplateClass (this=0x10) at TemplateClass.hpp:54 #1 0x00007f0dfc997878 in UEInstance::UEInstance (this=0x10) at UEInstance.hpp:11 #2 0x00007f0dfc998fdf in __gnu_cxx::new_allocator\u0026lt;UEInstance\u0026gt;::construct (this=0x7f0d967faf67, __p=0x10, __val=...) at /opt/gcc4.8.5/include/c++/4.8.5/ext/new_allocator.h:130 #3 0x00007f0dfc993a10 in std::list\u0026lt;UEInstance, std::allocator\u0026lt;UEInstance\u0026gt; \u0026gt;::_M_create_node (this=0x232488e8, __x=...) at /opt/gcc4.8.5/include/c++/4.8.5/bits/stl_list.h:487 #4 0x00007f0dfc991c19 in std::list\u0026lt;UEInstance, std::allocator\u0026lt;UEInstance\u0026gt; \u0026gt;::_M_insert (this=0x232488e8, __position=..., __x=...) at /opt/gcc4.8.5/include/c++/4.8.5/bits/stl_list.h:1553 #5 0x00007f0dfc99030e in std::list\u0026lt;UEInstance, std::allocator\u0026lt;UEInstance\u0026gt; \u0026gt;::push_back (this=0x232488e8, __x=...) at /opt/gcc4.8.5/include/c++/4.8.5/bits/stl_list.h:1016 #6 0x00007f0dfc97e005 in SessionProcess::createUEInstance (this=0x23248700, ueName=Calling) at SessionProcess.C:854 (gdb) bt #0 ___pthread_mutex_trylock (mutex=mutex@entry=0x28e8) at pthread_mutex_trylock.c:34 #1 0x00007f3c10ea81e4 in malloc_mutex_trylock_final (mutex=0x28a8) at include/jemalloc/internal/mutex.h:157 #2 malloc_mutex_lock (mutex=0x28a8, tsdn=0x7f3bb93febf0) at include/jemalloc/internal/mutex.h:216 #3 je_tcache_arena_associate (tsdn=0x7f3bb93febf0, tcache_slow=0x7f3bb93fecf0, tcache=0x7f3bb93fef48, arena=0x0) at src/tcache.c:588 #4 0x00007f3c10ea959e in arena_choose_impl (arena=0x0, internal=false, tsd=0x7f3bb93febf0) at include/jemalloc/internal/jemalloc_internal_inlines_b.h:60 #5 arena_choose (arena=0x0, tsd=0x7f3bb93febf0) at include/jemalloc/internal/jemalloc_internal_inlines_b.h:88 #6 je_tsd_tcache_data_init (tsd=tsd@entry=0x7f3bb93febf0) at src/tcache.c:740 #7 0x00007f3c10ea9753 in je_tsd_tcache_enabled_data_init (tsd=tsd@entry=0x7f3bb93febf0) at src/tcache.c:644 #8 0x00007f3c10eab1b9 in tsd_data_init (tsd=0x7f3bb93febf0) at src/tsd.c:244 #9 je_tsd_fetch_slow (tsd=0x7f3bb93febf0, minimal=minimal@entry=false) at src/tsd.c:311 #10 0x00007f3c10e53823 in tsd_fetch_impl (init=true, minimal=false) at include/jemalloc/internal/tsd.h:422 #11 tsd_fetch () at include/jemalloc/internal/tsd.h:448 #12 imalloc (dopts=\u0026lt;synthetic pointer\u0026gt;, sopts=\u0026lt;synthetic pointer\u0026gt;) at src/jemalloc.c:2681 #13 je_malloc_default (size=32) at src/jemalloc.c:2722 #14 0x000000000070df73 in mynew (size=32) at unix/memtest.C:30 #15 0x0000000000aea452 in TAs_slp::run (this=0x7f3bc24b1200, ptr=0x2281e770 \u0026lt;buffer_TMsg+1200\u0026gt;) at lib/as_slp.C:126 #16 0x00000000006587c8 in CWorkerThread::run (this=0x7f3bc553a020) at scmectrl/threadCom.C:149 #17 0x0000000000656b5a in CThread::ThreadFunction (point=0x7f3bc553a020) at scmectrl/threadBase.C:33 #18 0x00007f3c10ad132a in start_thread (arg=\u0026lt;optimized out\u0026gt;) at pthread_create.c:443 #19 0x00007f3c10b53370 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81 (gdb) f 15 #15 0x0000000000aea452 in TAs_slp::run (this=0x7f3bc24b1200, ptr=0x2281e770 \u0026lt;buffer_TMsg+1200\u0026gt;) at lib/as_slp.C:126 warning: Source file is more recent than executable. Python Exception \u0026lt;class \u0026#39;UnicodeDecodeError\u0026#39;\u0026gt;: \u0026#39;utf-8\u0026#39; codec can\u0026#39;t decode byte 0xbd in position 4955: invalid start byte 126 cm = new TComponentManager(this); (gdb) 看了几百个core, 发生错误的地方不尽相同, 没有发现明显的规律, 这使得问题分析变得困难起来, 因为之前就有定位过一个类似的情况: 进程在随机的地方coredump, 没有规律, 最后找了很久才找到是业务使用了已销毁的元素, 导致内存被破坏, 这种难点在于进程不会在代码有问题的地方重启, 而是继续跑一段时间, 访问到了被破坏的内存, 才会重启. 还是怀疑到了修改的代码身上, 但是对修改的代码反复检查, 实在是找不到值得怀疑的地方. 于是观察进程重启时的情况, 发现以下几个现象:\n进程重启的数据cpu占用会迅速上升, 内存占用会迅速上升 进程报告分配内存失败时, 内存剩余还很多, 不是内存用尽问题 基于这两个现象, 使用valgrind跟踪进程运行情况, 未发现进程有内存泄漏的情况.\n没有好的思路, 只能去网上检索相关问题, 大概找到了一下几个会影响进程申请内存的配置\nulimit -v 虚拟内存限制, 该配置会影响进程虚拟地址空间的大小, 如果过小的话, 虚拟地址空间不足, 会导致申请不到内存问题\novercommit_memory 0 表示内核将检查是否有足够的可用内存供应用进程使用, 如果有足够的可用内存, 内存申请允许, 否则, 内存申请失败, 并把错误返回给应用进程. 1 表示内核允许分配所有的物理内存, 而不管当前的内存状态如何. 2 表示内核允许分配超过所有物理内存和交换空间总和的内存.\n检查系统设置的虚拟内存限制为unlimited, overcommit_memory 配置为0, 遂将overcommit_memory 修改为1, 测试问题依旧.\n经过以上分析后, 已有情况如下: 配置:\n$ ulimit -a real-time non-blocking time (microseconds, -R) unlimited core file size (blocks, -c) unlimited data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 127250 max locked memory (kbytes, -l) 64 max memory size (kbytes, -m) unlimited open files (-n) 32768 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 127250 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited $ cat /proc/sys/vm/overcommit_memory 1 现象:\n内存充足, new/malloc失败 同等压力下进程数少更容易出现 启动终端下ulimit无限制 缩小问题范围 分析到这里, 并没有其他好的思路, 于是准备缩小问题范围. 检查问题是新引入还是旧版本就存在: 由于之前测试过程中, 一直采用的是cpu压测到70%, 但是存在的问题是, 旧版本性能跑不到新版本的量, 既然是存在内存分配问题, 那么如果将旧版本压测到新版本的量, 是否会出现此问题呢?, 于是马上进行了测试, 果然, 旧版本压力上去后, 也出现了这个情况, 且core基本类似. 说明该问题是一直存在的问题, 只是以前没有跑到那么大量, 没有出现而已. 至此已经可以排除优化代码问题.\n同时, 根据这个现象, 检查了系统上其他进程, 并没有出现此现象.\n接下来手动写了测试代码, 测试不停分配内存, 看是否能分配完主机内存, 结果是测试代码能完全分配主机内存, 这现象说明主机配置没问题, 是进程本身问题.\n于是怀疑是否是某些编译参数导致进程有限制? 首先想到的是进程架构, 检查发现编译时, 指定了 -m64, 编译的是64位进程, 不存在32位进程的虚拟地址空间限制.\nstrace跟踪 经过上述分析, 虽然问题缩小了范围, 但是仍然不好分析, 于是使用strace跟踪系统调用, 看出问题时的情况\n60122 18:03:29.113460 mmap(NULL, 8388608, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0 \u0026lt;unfinished ...\u0026gt; 52927 18:03:29.113508 \u0026lt;... futex resumed\u0026gt;) = 0 \u0026lt;0.000051\u0026gt; 60122 18:03:29.113636 \u0026lt;... mmap resumed\u0026gt;) = -1 ENOMEM (Cannot allocate memory) \u0026lt;0.000165\u0026gt; 使用strace跟踪进程系统调用发下, 在申请内存时, 系统调用mmap返回了 ENOMEM 错误, 提示无法分配内存\nlinux动态内存管理 两种动态内存管理的方法: 堆内存分配和mmap的内存分配, 此两种分配方法都是通过相应的Linux 系统调用来进行动态内存管理的. 具体使用哪一种方式分配, 根据glibc的实现, 主要取决于所需分配内存的大小.\n用brk实现进程里堆内存分配 在glibc中，当进程所需要的内存较小时, 小于128k的内存, 使用brk分配内存, 将_edata往高地址推(只分配虚拟空间，不对应物理内存(因此没有初始化)，第一次读/写数据时，引起内核缺页中断，内核才分配对应的物理内存，然后虚拟地址空间建立映射关系), 但是堆分配出来的内存空间, 系统一般不会回收, 只有当进程的堆大小到达最大限额时或者没有足够连续大小的空间来为进程继续分配所需内存时, 才会回收不用的堆内存. 在这种方式下, glibc会为进程堆维护一些固定大小的内存池以减少内存碎片.\n使用mmap的内存分配 在glibc中, 一般在比较大的内存分配时使用mmap系统调用, 它以页为单位来分配内存的(在Linux中, 一般一页大小定义为4K), 这不可避免会带来内存浪费, 但是当进程调用free释放所分配的内存时, glibc会立即调用unmmap, 把所分配的内存空间释放回系统.\nhttps://blog.csdn.net/yusiguyuan/article/details/39496057 https://www.cnblogs.com/Courage129/p/14232306.html https://www.cnblogs.com/Courage129/p/14231781.html https://www.cnblogs.com/arnoldlu/p/12156368.html\n从strace跟踪结果看, 是mmap系统调用返回了错误, 因此继续分析失败原因 查了一圈资料, max_map_count 参数可能会导致mmap返ENOMEM, 于是尝试调大该值, 测试仍没有效果.\nThis file contains the maximum number of memory map areas a process may have. Memory map areas are used as a side-effect of calling malloc, directly by mmap and mprotect, and also when loading shared libraries. While most applications need less than a thousand maps, certain programs, particularly malloc debuggers, may consume lots of them, e.g., up to one or two maps per allocation. The default value is 65536.\n分析到这里, 再次失去了方向, 为啥有大量可用内存, 地址空间未限制, 但是进程无法分配到内存呢?\n继续尝试 分析到这里, 感觉能用的办法都用了, 但是仍找不到问题, 于是乎, 我在代码入口处加了个分配内存测试的代码, 死循环分配, 观察下现象, 结果让人眼前一亮, 感觉看到了希望的曙光, 即使是不要业务, 单单内存分配, 进程也会在分配几百M后出现分配失败的问题, 这一下就让问题范围缩小到极致, 之前怀疑可能是内存破坏导致, 但是由于要性能测试时才会出现, 所以没有好方法跟踪到重启时的内存情况, 这个测试结果说明跟跑业务没关系, 在加上之前单独写了测试代码, 测试能够分配完所有内存, 这就让问题变得清晰起来, 为啥只有这个进程有内存限制, 但是测试进程没有呢, 于是马上想到, 之前查看的所有ulimit配置都是只看了启动终端下的结果, 没有看进程实际的limits, 果断查了正在重启的进程limits\n$ cat /proc/14402/limits Limit Soft Limit Hard Limit Units Max cpu time unlimited unlimited seconds Max file size unlimited unlimited bytes Max data size 900000000 unlimited bytes Max stack size 8388608 unlimited bytes Max core file size unlimited unlimited bytes Max resident set unlimited unlimited bytes Max processes 4096 31152 processes Max open files 65536 65536 files Max locked memory 65536 65536 bytes Max address space unlimited unlimited bytes Max file locks unlimited unlimited locks Max pending signals 31152 31152 signals Max msgqueue size 819200 819200 bytes Max nice priority 0 0 Max realtime priority 0 0 Max realtime timeout unlimited unlimited us scpas@eb60159\u0026gt; 果然一看结果, 和shell中设置的data size不一致, 启动进程的shell中设置的是unlimited, 但是进程的值限制到了900000000 通过prlimit命令修改了进程的data size, 果然, 进程不再重启, 问题解决\nprlimit -d=unlimited:unlimited -p 14402 但是为啥进程没有继承shell的ulimit配置, 而是被限制到了900000000呢, 思考了一下进程启动方式, 平台的进程是通过一个守护进程init 启动, 肯定是跟该进程有关, 于是尝试手动在终端下启动主进程, 查看果然, data size未unlimited, 内存分配没有问题.\n于是立马把守护进程代码翻出来检查, 终于找到了罪魁祸首:\nint setLimit() { struct rlimit x; int ret; ret = getrlimit(RLIMIT_CORE, \u0026amp;x); x.rlim_cur = x.rlim_max; ret = setrlimit(RLIMIT_CORE, \u0026amp;x); ret = getrlimit(RLIMIT_DATA, \u0026amp;x); x.rlim_cur = 900000000; ret = setrlimit(RLIMIT_DATA, \u0026amp;x); return ret; } init 进程在fork子进程时, 修改了data size的大小, 导致子进程的值和shell下的不同, 将改修改注释掉之后, 测试问题解决, 至此, 终于是找到了问题的原因, 短短的一行代码, 花了大功夫进行定位, 由于代码历史悠久, 缺乏文档, 很多问题定位极其困难.\n","permalink":"http://blog.cookbyte.net/post/malloc-error-on-linux/","summary":"问题背景 公司新产品需要高性能业务平台, 需要对现有的平台进行性能优化, 进行一系列优化后, 性能提升50%左右, 但是做性能测试时发现, 进程存在使用","title":"linux下new/malloc内存分配失败问题分析 - mmap系统调用返回ENOMEM"},{"content":"Jul 31, 2022 七月完结 健身🏋️24小时 跑步🏃66公里 骑行🚴322公里 All ","permalink":"http://blog.cookbyte.net/journal/2022-07-31/","summary":"Jul 31, 2022 七月完结 健身🏋️24小时 跑步🏃66公里 骑行🚴322公里 All","title":"2022-07-31"},{"content":" 字符串 字符串可以使用range访问 s := \u0026#34;test\u0026#34; for i, v := range s { // the type of v is rune fmt.Printf(\u0026#34;index %d, value %c\u0026#34;) } s := \u0026quot;test\u0026quot; s是常量, 无法通过下标进行修改, 需要转换成slice操作\ns = s + s1 此操作会生成新字符串, 使用 strings.Join() 效率更高\nmake chan slice 和 map 引用类型使用 make 初始化 new 值类型可使用 new fmt fmr.Println 是使用 %v 格式化参数, 并在最后追加换行符 const 常量必须是 数字 字符串 布尔值\n常量的值必须是能够在编译时就能够确定的; 你可以在其赋值表达式中涉及计算过程, 但是所有用于计算的值必须在编译期间就能获得\n变量 变量可以编译期间就被赋值\n:= 是声明变量的首选形式, 但是它只能被用在函数体内, 而不可以用于全局变量的声明与赋值\n指针 函数返回局部变量的地址是安全的, 如下, 指针p依然引用v, v不会被回收 var p = f() func f() *int { v := 1 return \u0026amp;v } ","permalink":"http://blog.cookbyte.net/post/learn-golang/","summary":"字符串 字符串可以使用range访问 s := \u0026#34;test\u0026#34; for i, v := range s { // the type of v is rune fmt.Printf(\u0026#34;index %d, value %c\u0026#34;) } s := \u0026quot;test\u0026quot; s是常量, 无法通过下标进行修改, 需要转换成slice操作","title":"Golang 学习笔记"},{"content":"英语语法 有哪些\u0026quot;动作词\u0026quot;(动词)? 可以独立完成的动作｜主语+(不及物)动词\n有一个动作的承受者 ｜ 主语+(及物)动词+宾语\n有两个动作承受者 ｜ 主语+(双及物)动词+间接宾语+直接宾语\n只有一个动作承受者(不同于2) ｜ 主语+(复杂及物)动词+宾语+(宾语)补语\n把这个词后面的信息赋予给前者 ｜ 主语+(系)动词+(主语)补语(又称表语)\n","permalink":"http://blog.cookbyte.net/english/grammar/","summary":"英语语法 有哪些\u0026quot;动作词\u0026quot;(动词)? 可以独立完成的动作｜主语+(不及物)动词 有一个动作的承受者 ｜ 主语+(及物)动词+宾语 有","title":"grammar"},{"content":"Jul 17, 2022 # 完美一周 第一个完美一周, 继续加油!\n","permalink":"http://blog.cookbyte.net/fitness/2022-07-17/","summary":"Jul 17, 2022 # 完美一周 第一个完美一周, 继续加油!","title":"2022-07-17"},{"content":"# Refrence 英语进阶指南 English-level-up-tips\n李笑来英语学习 everyone-can-use-english\n旋元佑进阶文法 EnglishGrammarBook\n","permalink":"http://blog.cookbyte.net/english/learn-reference/","summary":"# Refrence 英语进阶指南 English-level-up-tips 李笑来英语学习 everyone-can-use-english 旋元佑进阶文法 EnglishGrammarBook","title":"learn-reference"},{"content":"May 6, 2022 This is k\u0026rsquo;s blog.\n","permalink":"http://blog.cookbyte.net/about/","summary":"May 6, 2022 This is k\u0026rsquo;s blog.","title":"about"},{"content":"小米 6 提取微信聊天记录笔记 通过 MI6 本地备份提取聊天记录 要导出微信安卓客户端的聊天记录，首先得找到聊天记录的数据库。 安卓客户端的聊天记录储存在私有目录 /data/data/com.tencent.mm/MicroMsg 下，这个目录需要 root 权限才能进去，但是，那样太太太麻烦了，好在我们 MI6 有本地备份的功能，利用这个功能。我们轻而易举就可以获得数据库。\n需要的工具 abe.jar IMEI.java IMEI.class MapTest.java MapTest.class\n环境 需要安装 Java 环境，未安装可以搜索 Java 安装\n提取数据库 首先到手机：设置-\u0026gt;更多设置-\u0026gt;备份和重置-\u0026gt;本地备份 里面点击新建备份，选择软件程序中的微信进行备份\n然后到文件管理 /内部储存设备/MIUI/backup/ALLBackup/ 下将备份的文件夹复制到电脑\n接下来是从备份的文件中提取微信聊天记录，通过WinHex软件查看备份包信息，发现 miui 备份包是在原生安卓的备份基础上多加了一个文件头。选中多余的文件头，按delete键删除，点击保存。修改后的文件就是标准的原生安卓备份文件。\n接下来，将修改后的com.tencent.mm.bak 处理成原文件，首先将abe.jar复制到和com.tencent.mm.bak同一个文件夹下 终端执行： java -jar abe.jar unpack com.tencent.mm.bak mm.tar 会生成一个 mm.tar 文件，用解压软件将这个包解压，会得到一个apps文件夹，apps文件夹下面有com.tencent.mm文件夹，聊天记录数据库就存在apps/com.tencent.mm/r/MicroMsg/ 下，打开文件夹会发现里面有 32 位字符 (MD5 值) 的文件夹 (登录过多个用户的有多个)，打开此文件夹其中EnMicroMsg.db就是要找的数据库文件。\n生成数据库密码 找到聊天数据库了，但是目前还不能得到聊天记录，因为这个数据库是sqlcipher加密数据库，需要密码才能打开。\n数据库密码有很多种生成方式：\n手机IMEI+uin(微信用户 id userinformation) 将拼接的字符串MD5 加密取前 7 位\n如IMEI为123456，uin为abc，则拼接后的字符串为123456abc 将此字符串用MD5 加密(32 位) 后\n为df10ef8509dc176d733d59549e7dbfaf 那么前 7 位df10ef8 就是数据库的密码，由于有的手机是双卡，有多个IMEI，或者当手机获取不到IMEI时会用默认字符串1234567890ABCDEF来代替，由于种种原因，并不是所有人都能得出正确的密码，此时我们可以换一种方法。\n反序列化CompatibleInfo.cfg和systemInfo.cfg\n不管是否有多个IMEI ，或者是微信客户端没有获取到IMEI，而使用默认字符串代替，微信客户端都会将使用的信息保存在MicroMsg文件夹下面的CompatibleInfo.cfg和systemInfo.cfg文件中，可以通过这两个文件来得到正确的密码，但是这两个文件需要处理才能看到信息。\n使用 hook 方式得到数据库的密码，这个方法最有效参考\n暴力破解\n\u0026hellip;\n使用反序列化的方式获得密码 由于我的手机有多个IMEI码，试了半天出来的密码都不对，故使用此方法来得到密码。 首先将CompatibleInfo.cfg和systemInfo.cfg以及EnMicroMsg.db复制出来，将下面这段代码保存到IMEI.java文件\nimport java.io.FileInputStream; import java.io.ObjectInputStream; import java.security.MessageDigest; import java.util.HashMap; public class IMEI { public static void main(String[] args) { try { ObjectInputStream in = new ObjectInputStream(new FileInputStream( args[0])); Object DL = in.readObject(); HashMap hashWithOutFormat = (HashMap) DL; ObjectInputStream in1 = new ObjectInputStream(new FileInputStream( args[1])); Object DJ = in1.readObject(); HashMap hashWithOutFormat1 = (HashMap) DJ; String s = String.valueOf(hashWithOutFormat1.get(Integer .valueOf(258))); // 取手机的 IMEI System.out.println(\u0026#34;The IMEI is : \u0026#34; + s); String uin = String.valueOf(hashWithOutFormat.get(Integer.valueOf(1))); System.out.println(\u0026#34;The uin is : \u0026#34; + uin); s = s + uin; //合并到一个字符串 s = encode(s); // hash System.out.println(\u0026#34;The Key is : \u0026#34; + s.substring(0, 7)); in.close(); in1.close(); } catch (Exception e) { e.printStackTrace(); } } public static String encode(String content) { try { MessageDigest digest = MessageDigest.getInstance(\u0026#34;MD5\u0026#34;); digest.update(content.getBytes()); return getEncode32(digest); } catch (Exception e) { e.printStackTrace(); } return null; } private static String getEncode32(MessageDigest digest) { StringBuilder builder = new StringBuilder(); for (byte b : digest.digest()) { builder.append(Integer.toHexString((b \u0026gt;\u0026gt; 4) \u0026amp; 0xf)); builder.append(Integer.toHexString(b \u0026amp; 0xf)); } return builder.toString(); } } 将这段代码保存到IMEI.java文件，将三个文件放在相同目录下 终端运行\njavac IMEI.java java IMEI systemInfo.cfg CompatibleInfo.cfg 运行完成后就会得到密码 参考链接\n解密数据库 Windows 用户可以使用sqlcipher.exe软件来查看数据库 打开数据库，输入密码，就可以查看数据库中的表 文字聊天记录储存在message表中，可以选中表，点击软件的右上角 file-export 导出表到csv文件，可以通过 Excell 查看表中的信息 执行这段命令可以查看制定对象的聊天记录\nselect datetime(subStr(cast(m.createTime as text),1,10),\u0026#39;unixepoch\u0026#39;, \u0026#39;localtime\u0026#39;) as theTime,case m.isSend when 0 then r.nickname when 1 then \u0026#39;我\u0026#39;end as person,m.content from message m inner join rcontact r on m.talker = r.username where m.type=1 and r.nickname = \u0026#39;对方微信昵称\u0026#39; Linux 用户可以使用sqlcipher来解密\nsudo apt-get update sudo apt-get install sqlcipher sqlcipher EnMicroMsg.db \u0026#39;PRAGMA key = \u0026#34;yourkey\u0026#34;; PRAGMA cipher_use_hmac = off; PRAGMA kdf_iter = 4000; ATTACH DATABASE \u0026#34;decrypted_database.db\u0026#34; AS decrypted_database KEY \u0026#34;\u0026#34;;SELECT sqlcipher_export(\u0026#34;decrypted_database\u0026#34;);DETACH DATABASE decrypted_database;\u0026#39; 执行上面的命令之后会得到一个解密后的数据库decrypted_database.db，可以使用数据库软件查看\n得到数据库之后可以分析一下你的聊天记录，顺便制作一个词云来给你的心上人看一下你们都聊了啥\u0026#x1f440;\n看了一下我和女票日消息数走势，最多一天竟然发了 809 条\u0026#x1f602;\n","permalink":"http://blog.cookbyte.net/post/xiaomi-wechat-history-export/","summary":"小米 6 提取微信聊天记录笔记 通过 MI6 本地备份提取聊天记录 要导出微信安卓客户端的聊天记录，首先得找到聊天记录的数据库。 安卓客户端的聊天记录储存在私","title":"小米 6 提取微信聊天记录笔记"}]