今天,我们很高兴地宣布 Fedora Asahi Remix 39 现已 正式发布!这个 Remix 版基于 Fedora Linux 39,通过广泛的平台和设备支持,在苹果芯片 Mac 上提供了出色的体验。
分类: Linux
Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。它主要用于基于Intel x86系列CPU的计算机上。这个系统是由全世界各地的成千上万的程序员设计和实现的。其目的是建立不受任何商品化软件的版权制约的、全世界都能自由使用的Unix兼容产品。
Musl libc:为什么我们会需要另一个 libc?
如果你是一个 Linux 用户,那你一定至少听说过 Glibc 的鼎鼎大名,或者甚至在日常使用中碰到不少关于它的问题,例如 Glibc 版本不匹配等问题。而本文的主角—— Musl libc 与之相比就要默默无闻的多,毕竟绝大多数的 Linux 发行版使用的 libc 库都是 Glibc,只有 Alpine Linux 等极少数 Linux 发行版才会使用 Musl libc,并且还会遇到诸如闭源 JDK 无法正常使用等问题的困扰,那么,为什么我们会需要另一个 libc 函数库呢?
Glibc 并不完美
Glibc 作为目前使用最广泛的 libc 函数库,虽然拥有最广泛的发行版支持和用户群体,并且在兼容性和性能方面也存在一些优势,但它并不完美,有三个问题严重困扰着它。
代码库陈旧
Glibc 拥有悠久的历史——对于软件而言这可能并不一定是一句赞誉,尤其是当你需要处理上世纪九十年代就存在的代码库时。三十多年来,程序员们编写 C 程序的方式并不是一成不变的,某些在那个年代被认为是好习惯或者是必须的编程方式在今天看来可能完全不合时宜,诸如叠床架屋的宏等等。这些问题严重拖累了 Glibc 的源码可读性,例如下面这一段源代码,摘自 Glibc 的 fopen 函数。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
FILE * __fopen_internal (const char *filename, const char *mode, int is32) { struct locked_FILE { struct _IO_FILE_plus fp; #ifdef _IO_MTSAFE_IO _IO_lock_t lock; #endif struct _IO_wide_data wd; } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE)); if (new_f == NULL) return NULL; #ifdef _IO_MTSAFE_IO new_f->fp.file._lock = &new_f->lock; #endif _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps); _IO_JUMPS (&new_f->fp) = &_IO_file_jumps; _IO_new_file_init_internal (&new_f->fp); if (_IO_file_fopen ((FILE *) new_f, filename, mode, is32) != NULL) return __fopen_maybe_mmap (&new_f->fp.file); _IO_un_link (&new_f->fp); free (new_f); return NULL; } |
作为对比,下面是 Musl 库中同样对 fopen 函数的实现
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
FILE *fopen(const char *restrict filename, const char *restrict mode) { FILE *f; int fd; int flags; /* Check for valid initial mode character */ if (!strchr("rwa", *mode)) { errno = EINVAL; return 0; } /* Compute the flags to pass to open() */ flags = __fmodeflags(mode); fd = sys_open(filename, flags, 0666); if (fd < 0) return 0; if (flags & O_CLOEXEC) __syscall(SYS_fcntl, fd, F_SETFD, FD_CLOEXEC); f = __fdopen(fd, mode); if (f) return f; __syscall(SYS_close, fd); return 0; } |
两者的可读性差距不言自明,笔者在这里并非是要批判 Glibc 的代码风格,但对于初次上手阅读源码的人来说,显然是 Musl 的风格更加友好,更便于理解。
体积过大
由于 Glibc 相较体积更加关注性能,因此其链接生成的二进制文件相较于 Musl uClibc 等专注于嵌入式等场合的库来说要大很多,而这些场合往往非常关注几百 K 大小的区别,因为 SRAM 的大小往往关乎整体开发板的成本。
下面这张表展示了不同 libc 库编译的文件大小。
| 尺寸对比 | musl | uClibc | dietlibc | glibc |
|---|---|---|---|---|
| .a | 426k | 500k | 120k | 2.0M |
| .so | 527k | 560k | 185k | 7.9M |
| 静态最小 | 1.8k | 5k | 0.2k | 662k |
| 静态输出 | 13k | 70k | 6k | 662k |
可以看出 Glibc 在程序大小上明显大于其他 libc 库,此外,这里的静态也是有水分的,这就引出了 Glibc 的下一个问题,也是最重要的问题之一:静态链接。
对静态链接支持不佳
理论上来说,Glibc 是支持静态链接的。但,这也仅仅是从理论上来说,由于一些历史遗留问题(当然,也包括对功能实现的考虑)Glibc 的静态链接并不是真正的静态链接:如果你的程序中使用了某些不支持静态链接的特性(这一点在大型软件中非常常见),那么即便你在链接时选择静态链接,生成出来的程序实际上仍然是依赖于 Glibc 动态库的,一旦你尝试删除掉它,你立马就会发现这些“静态”链接的程序统统罢工不干了。
谁在用 Musl?
在发行版中,主要是 Alpine Linux,作为最特立独行的 Linux 发行版之一,它选用了 Musl + Busybox 的组合,而非通常的 Glibc + Coreutils,这使得它的最小安装可以控制在惊人的 5 MB 之内!相比之下,普通的 CentOS 最小安装则需要 200 MB 左右,这一点使得它在嵌入式等对内存占用极为敏感的场合占据了相当的优势。
此外,Musl 从设计之初就很关注静态链接的可用性,因此它完全可以被静态链接进其他程序中,不存在 Glibc 对动态库的依赖问题,这一点也有助于缓解不同版本 libc 之间的兼容性问题——只要我把万物都静态链接进去,就不存在版本问题了。当然,这种做法也会带来体积膨胀等问题,所以并不是一个太好的解决方案。
Musl 的问题与未来
虽然拥有诸多优点,但 Musl 在性能方面逊于 Glibc 也是不争的事实,毕竟简化实现的代价就包括牺牲性能,不过这一点并非不可拯救,通过使用开源的 malloc 实现(诸如微软的实现)替换这些对性能影响较大的热点函数,就可以在很大程度上解决性能方面的问题。
同时,试图取代或至少部分取代 Glibc 的库也并不止 Musl 一个, 例如用于 AOSP 项目上的 bionic,以及广泛应用于各种嵌入式开发中的 uClibc 等。与它们相比,Musl 背后既没有 Google 这样的大公司撑腰,在压缩体积方面做的也不够极致,相较之下就没有那么受到开发者们的青睐,在新功能和新特性跟进上也不是非常积极。
出于这些原因,也许 Musl 在今后的很长一段时间内会继续保持这种“小而美”的特点,但这对于我们来说并非就是一件坏事,能够看到与 GNU Glibc 风格截然不同的另一种实现,对于 Linux 社区的多样性,以及对于我们这些学习者来说,何尝不是一件美事?
参考链接
Box86/Box64 vs QEMU vs FEX (vs Rosetta2)
Comparing performances
I decided to compare the performances of the OpenSource Linux Userspace Emulator that allows you to run x86/x86_64 apps on ARM linux. There are QEMU-user, FEX-emu and Box86/Box64.
How to bench Linux userspace emulator
Test will consist of the bench I already used a couple of time, and that can be run as native or emulated:
- 7-zip integrated benchmark, that contains mostly integer code (no x87 or SSE), and can be used as a baseline to see the pure x86 code translation efficiency. The version 16 present in Ubuntu was used for those tests.
- dav1d, an opensource video transcoding tool, that includes hand-optimized SSE assembly code (SSE3 or more).
- glmark2 that is GL limited and should run at mostly native speed (as long as GL is hardware accelerated). I couldn’t install the armhf version of glmark2 on Ubuntu, so only the native 64bits version was benchmarked.
- openarena, that contains x87 code, and a JIT, and, in that config, is very much GPU limited, and so should be running very close to the native speed (again, as long as GL is still hardware accelerated).
The 7z, dav1d and glmark2 bench are described here, and the openarena one there.
I’ll also do some quick bench not available natively. The fps will simply be measured with HUD_GALLIUM=fps on a stable and reproducible moment in the game:
- WorldOfGoo: The game has simple graphics, it should run fine. Measures will be done on the Title screen.
- FTL: that I added to the bench after doing the QEMU measures. Measures will be done on the 1st Tutorial screen, while the game is paused.
- CINEBENCH r15: A benchmark based on a raytracing engine. Lots of SSE (SSE2 and more) code here. Use multi-core also. Does include a CPU bench and OpenGL bench, but only the CPU one is used here. It provides a simple number indicating the performance (the higher, the better).
To install WorldOfGoo, the “uname” trick will be used, as this allows to choose x86 or x86_64 installation (without the trick, the installer doesn’t recognise “aarch64” platform and fallback to x86). WorldOfGoo will run at 1920×1080 fullscreen.
CINEBENCH r15. This one needs Wine, and a 64bits version of it. It’s a benchmark with the CINEMA 4D Engine.
After some some testing, I realized that both openarena and WorldOfGoo mainly use x87 code, at least for the 32bits version of WorldOfGoo. Both QEMU and FEX seem to use use Softfloat for it, to keep the 80bits precision, while box uses hardware float (with some tricks to keep 80bits when needed, like on some data copy used by old games), so I decided to also check the menu page of FTL, that I know use SSE code. But I didn’t test on QEMU (it’s not hardware accelerated anyway, so it would be too slow). FTL will run at default resolution of 1280×720 windowed. I’ll launch the tutorial, answer to the 1st dialog box and mesure the fps at this point.
云服务器 ECS Linux SSH 客户端断开后保持进程继续运行配置方法
在云服务器 ECS Linux 系统中,通常我们在执行一些运行时间比较长的任务时,必须等待执行完毕才能断开 SSH 连接或关闭客户端软件,否则可能会导致执行中断。
本文介绍几种保障程序在用户退出登录后持续运行的方法。
使用 nohup 执行
nohup 的作用顾名思义,它使得后面的命令不会响应挂断(SIGHUP)信号。也就是说,通过远程登录执行 nohup 后,即使退出登录后,程序还是会正常执行。通常情况下,nohup 命令最后会跟上 & 字符,表示将这个命令放至后台执行,这样才能真正做到将这个命令放至后台持续的执行。
操作示例:
1. 正常的执行命令为 bash hello.sh,执行结果为每秒输出一行的小程序:
2. 在命令头尾分别加上 nohup 和 &,变为 nohup bash hello.sh &,可以看到 nohup 输出了一行信息,再按一下回车键就跳回了 shell 命令行,此时命令已经在后台执行了,nohup 将命令的输出重定向至当前目录的 nohup.out 文件中。同时注意到 nohup 会将对应程序的 PID 输出,PID 可用于需要中断进程时 kill 进程。
3. 通过 tail -f nohup.out 可以持续的查看 nohup.out 的输出,达到监视程序的效果。
4. 在命令中也可以使用重定向将程序的输出改为自己想要的文件名,如 nohup bash hello.sh >hello.log &,则程序的输出就会写到 hello.log 文件中。
5. 若程序不会自动退出,那么此时需要使用 kill 命令来结束进程。比如,可以使用命令 kill -TRM <PID> 来操作,其中 PID 即为之前 nohup 输出的值,在此例中该值为 1231。
使用限制:
nohup 通常用于执行无干预的自动化程序或脚本,无法完成带有交互的操作。
使用 screen 执行
安装 sceen 工具
Linux 系统默认未自带 screen 工具,需要先进行安装:
- CentOS 系列系统: yum install screen
- Ubuntu 系列系统: sudo apt-get install screen
使用简介
1. 创建 screen 窗口
|
1 2 3 4 |
$ screen -S name # name可以设置为ssh、ftp,用于标注该 screen 窗口用途 # 示例: screen -S ftp |
2. 列出 screen 进程,并进入所需 screen
|
1 2 |
$ screen -ls ##列出 screen 进程列表 |
然后进行所需操作,比如运行脚本、执行程序等等。
如下图示例:创建ftp连接后台下载传输文件
3. 退出保存
前述 ftp 操作示例开始传输后,在窗口中键入Ctrl+a 键,再按下 d 键,就可以退出 SSH 登录,但不会影响 screen 程序的执行。
4. 保存会话以便继续执行
可以利用 screen 这种功能来管理的远程会话。操作步骤概述:
- 正常 SSH 登录服务器
- 创建 screen 窗口
- 执行所需任务
- 需要临时中断退出时,Ctrl + a 再按下 Ctrl + d 保存退出
- 需要继续工作时,再次 SSH 登录服务器,然后直接执行 screen -r -d 恢复会话即可。
参考链接
Centos 7/RedFlag 7/Asianux 7安装|更新Git
背景描述
Centos 7/RedFlag 7/Asianux 7 上的 Git 版本太陈旧,在使用过程中会遇到问题,比如不支持目录分支(release/v01 这种格式会在检出的时候报错),因此需要升级 Git 版本。
|
1 2 3 4 5 6 |
$ git --version git version 1.8.3.1 # 系统版本:(CentOS 7.6) $ cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) |
安装依赖包
源代码安装和编译git,需安装一些依赖。
|
1 2 3 |
$ yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel asciidoc -y $ yum install gcc perl-ExtUtils-MakeMaker -y |
卸载旧版本
|
1 |
$ yum remove git |
安装步骤
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ cd /usr/local/src/ $ wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.38.1.tar.gz # 或者 wget https://www.mobibrw.com/wp-content/uploads/2023/09/git-2.38.1.tar.gz $ tar -xvf git-2.38.1.tar.gz $ cd git-2.38.1/ $ ./configure --prefix=/usr/local/git all $ make -j && make install # 链接到系统默认搜索路径 $ ln -s /usr/local/git/bin/git /usr/bin/git |
验证版本
|
1 2 |
[root@localhost ~]# git --version git version 2.38.1 |
参考链接
使用SSH Key访问服务器
1. 配置
生成的 SSH Key 默认存储在 ~/.ssh 目录下,可以使用 ls ~/.ssh 查看之前是否已经生成过 SSH Key,如果提示 No such file or directory 可以使用如下指令用于生成 SSH Key:
|
1 |
ssh-keygen |
接着会询问保存文件的位置以及是否要设定 passphrase,如果设定了 passsphrase 那么每次使用该 SSH Key 的时候都需要输入这个 passsphrase,可以根据自己对安全性的需求设定,空白表示不设定。
如果采用默认的设置,那么会在 ~/.ssh 路径下生成两个 Key,一个私钥 id_rsa ,另一个公钥 id_rsa.pub,私钥需要好好保管。
2. 使用 ssh-copy-id
如果操作系统中有 ssh-copy-id,那么可以直接使用以下命令设置:
|
1 |
ssh-copy-id -i ~/.ssh/id_rsa.pub username@server |
- username:连接服务器的用户名
- server:服务器的域名或者 ip 地址
- ~/.ssh/id_rsa.pub:默认的公钥地址,如果修改过 SSH Key 存储地址,请填写对应地址
3. 复制公钥到服务器的 authorized_keys 中
|
1 |
ssh username@server 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys' < ~/.ssh/id_rsa.pub |
上述命令用于在远程服务器上创建 ~/.ssh 文件夹并用本机的公钥创建服务器上的 ~/.ssh/authorized_keys 文件,接着设置权限:
|
1 |
ssh username@server 'chmod 700 -R ~/.ssh && chmod 600 ~/.ssh/authorized_keys' |
4. 验证
经过上述的配置之后,可以再次进行 SSH 连接验证配置是否生效:
|
1 |
ssh username@server |
如果不需要输入密码就能够连接服务器,说明设置生效。
参考链接
Ubuntu 22.04 (x64)树莓派4B(Raspberry Pi 4B)源代码编译
树莓派上的操作
树莓派使用的系统是通过 Raspberry Pi Imager 安装的 2023-05-03-raspios-bullseye-armhf.img.xz 。
1.升级到最新版内核保证与下载的内核源码版本一致
|
1 |
$ sudo rpi-update |
2.升级完整后重启
|
1 |
$ sudo reboot |
3.查看内核版本
|
1 |
$ uname -r |
4.把最新版本的内核配置保存到.config中,以备以后编译内核使用
|
1 |
$ sudo modprobe configs |
文件被存储到了/proc/config.gz中。
目前最新版本是 6.1.12,当前内核启动默认会切换到 64位内核了,即使安装的是32位系统镜像也是这样。
如果想从32位内核启动,那么需要在 config.txt 中配置 arm_64bit。
多线程压缩工具pigz使用
学习Linux系统时都会学习这么几个压缩工具:gzip、bzip2、zip、xz,以及相关的解压工具。关于这几个工具的使用和相互之间的压缩比以及压缩时间对比可以看:Linux中归档压缩工具学习
那么Pigz是什么呢?简单的说,就是支持并行压缩的gzip。Pigz默认用当前逻辑cpu个数来并发压缩,无法检测个数的话,则默认并发8个线程,也可以使用-p指定线程数。需要注意的是其CPU使用比较高。
安装
|
1 2 3 4 5 |
# centos $ yum install pigz # debian / ubuntu $ sudo apt-ge tinstall pigz |
使用方法
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
$ pigz --help Usage: pigz [options] [files ...] will compress files in place, adding the suffix '.gz'. If no files are specified, stdin will be compressed to stdout. pigz does what gzip does, but spreads the work over multiple processors and cores when compressing. Options: -0 to -9, -11 Compression level (11 is much slower, a few % better) --fast, --best Compression levels 1 and 9 respectively -b, --blocksize mmm Set compression block size to mmmK (default 128K) -c, --stdout Write all processed output to stdout (won't delete) -d, --decompress Decompress the compressed input -f, --force Force overwrite, compress .gz, links, and to terminal -F --first Do iterations first, before block split for -11 -h, --help Display a help screen and quit -i, --independent Compress blocks independently for damage recovery -I, --iterations n Number of iterations for -11 optimization -k, --keep Do not delete original file after processing -K, --zip Compress to PKWare zip (.zip) single entry format -l, --list List the contents of the compressed input -L, --license Display the pigz license and quit -M, --maxsplits n Maximum number of split blocks for -11 -n, --no-name Do not store or restore file name in/from header -N, --name Store/restore file name and mod time in/from header -O --oneblock Do not split into smaller blocks for -11 -p, --processes n Allow up to n compression threads (default is the number of online processors, or 8 if unknown) -q, --quiet Print no messages, even on error -r, --recursive Process the contents of all subdirectories -R, --rsyncable Input-determined block locations for rsync -S, --suffix .sss Use suffix .sss instead of .gz (for compression) -t, --test Test the integrity of the compressed input -T, --no-time Do not store or restore mod time in/from header -v, --verbose Provide more verbose output -V --version Show the version of pigz -z, --zlib Compress to zlib (.zz) instead of gzip format -- All arguments after "--" are treated as files |
原目录大小:
|
1 2 3 4 5 6 |
[20:30 root@hulab /DataBase/Human/hg19]$ du -h 8.1G ./refgenome 1.4G ./encode_anno 4.2G ./hg19_index/hg19 8.1G ./hg19_index 18G . |
接下来我们分别使用gzip以及不同线程数的pigz对h19_index目录进行压缩,比较其运行时间。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
### 使用gzip进行压缩(单线程) [20:30 root@hulab /DataBase/Human/hg19]$ time tar -czvf index.tar.gz hg19_index/ hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 5m28.824s user 5m3.866s sys 0m35.314s ### 使用4线程的pigz进行压缩 [20:36 root@hulab /DataBase/Human/hg19]$ ls encode_anno hg19_index index.tar.gz refgenome [20:38 root@hulab /DataBase/Human/hg19]$ time tar -cvf - hg19_index/ | pigz -p 4 > index_p4.tar.gz hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 1m18.236s user 5m22.578s sys 0m35.933s ### 使用8线程的pigz进行压缩 [20:42 root@hulab /DataBase/Human/hg19]$ time tar -cvf - hg19_index/ | pigz -p 8 > index_p8.tar.gz hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 0m42.670s user 5m48.527s sys 0m28.240s ### 使用16线程的pigz进行压缩 [20:43 root@hulab /DataBase/Human/hg19]$ time tar -cvf - hg19_index/ | pigz -p 16 > index_p16.tar.gz hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 0m23.643s user 6m24.054s sys 0m24.923s ### 使用32线程的pigz进行压缩 [20:43 root@hulab /DataBase/Human/hg19]$ time tar -cvf - hg19_index/ | pigz -p 32 > index_p32.tar.gz hg19_index/ hg19_index/hg19.tar.gz hg19_index/hg19/ hg19_index/hg19/genome.8.ht2 hg19_index/hg19/genome.5.ht2 hg19_index/hg19/genome.7.ht2 hg19_index/hg19/genome.6.ht2 hg19_index/hg19/genome.4.ht2 hg19_index/hg19/make_hg19.sh hg19_index/hg19/genome.3.ht2 hg19_index/hg19/genome.1.ht2 hg19_index/hg19/genome.2.ht2 real 0m17.523s user 7m27.479s sys 0m29.283s ### 解压文件 [21:00 root@hulab /DataBase/Human/hg19]$ time pigz -p 8 -d index_p8.tar.gz real 0m27.717s user 0m30.070s sys 0m22.515s |
各个压缩时间的比较:
| 程序 | 线程数 | 时间 |
|---|---|---|
| gzip | 1 | 5m28.824s |
| pigz | 4 | 1m18.236s |
| pigz | 8 | 0m42.670s |
| pigz | 16 | 0m23.643s |
| pigz | 32 | 0m17.523s |
从上面可以看出,使用多线程pigz进行压缩能进行大大的缩短压缩时间,特别是从单线程的gzip到4线程的pigz压缩时间缩短了4倍,继续加多线程数,压缩时间减少逐渐不那么明显。
虽然pigz能大幅度的缩短运行时间,但这是以牺牲cpu为代价的,所以对于cpu使用较高的场景不太宜使用较高的线程数,一般而言使用4线程或8线程较为合适。
参考链接
Flutter The Linux toolchain CMake build dependency (CMake 3.14 or higher is required. You are running version 3.10.2)
在 ubuntu 22.04 通过 snap 安装了 Flutter SDK(当前是Flutter 3.3.4),如果第三方的依赖了 CMake 3.10.2 更高的版本,会在编译的时候报错:
|
1 |
CMake 3.14 or higher is required. You are running version 3.10.2 |
这个报错的原因是由于 snap 安装的 Flutter SDK 构建了一个沙箱环境,在这个环境中的 CMake 是 3.10.2 版本,不管系统安装的是哪个版本的 CMake ,都是无效的。
要解决这个问题,或者等待 snap 的 Flutter SDK 更新版本,或者参照 Linux install Flutter 的说明,手工安装并配置 Flutter SDK 。
可以参考如下代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$ sudo snap remove flutter $ sudo apt-get install curl $ sudo apt-get install clang $ sudo apt-get install git $ sudo apt-get install ninja-build $ sudo apt-get install pkg-config $ sudo apt install gettext $ sudo apt-get install libgtk-3-dev $ sudo apt install libstdc++-12-dev $ git clone https://github.com/flutter/flutter.git -b stable $ export PATH="$PATH:`pwd`/flutter/bin" $ cd `pwd`/flutter/bin $ ./flutter doctor |
参考链接
Flutter 3.0实现Linux本地化/国际化
参照 Flutter 2.8.1本地化/国际化应用程序名称 可以实现 Android/macOS/iOS/Web 的应用名称相关的国际化。但是在 Linux 应用上如何相同的功能,目前暂时没有一个统一的标准。
研究了许久,终于基本上算是搞定,解决方案如下:
使用 gettext 来实现国际化相关的功能。
首先配置,调整工程的目录如下:
project/
project/linux
project/linux/flutter
project/linux/flutter/CMakeLists.txt
project/linux/locale/en_US/app.po
project/linux/locale/zh_CN/app.mo
project/linux/locale/CMakeLists.txt
project/linux/CMakeLists.txt
project/linux/main.cc
project/linux/my_application.cc
project/linux/my_application.h
对应语言 i18n 相关配置文件的内容如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(OUTPUT_NAME "app") set(LOCALE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(LOCALE_BUILD_DIR "${CMAKE_BINARY_DIR}/locale") set(LOCALE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/locale") # Setting up Internationalisation (i18n) find_package (Intl REQUIRED) if (Intl_FOUND) message(STATUS "Internationalization (i18n) found:") message(STATUS " INTL_INCLUDE_DIRS: ${Intl_INCLUDE_DIRS}") message(STATUS " INTL_LIBRARIES: ${Intl_LIBRARIES}") message(STATUS " Version: ${Intl_VERSION}") include_directories(${Intl_INCLUDE_DIRS}) link_directories(${Intl_LIBRARY_DIRS}) else () message(STATUS "Internationalization (i18n) Not found!") endif () find_package(Gettext REQUIRED) if (Gettext_FOUND) message(STATUS "Gettext found:") message(STATUS " Version: ${GETTEXT_VERSION_STRING}") else () message(STATUS "Gettext Not found!") endif () find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext) find_program(GETTEXT_MSGMERGE_EXECUTABLE msgmerge) find_program(GETTEXT_MSGFMT_EXECUTABLE msgfmt) if (GETTEXT_XGETTEXT_EXECUTABLE) message(DEBUG " xgettext: ${GETTEXT_XGETTEXT_EXECUTABLE}") file(GLOB_RECURSE PO_FILES RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/*.po) add_custom_target( pot-update COMMENT "pot-update: Done." DEPENDS ${LOCALE_DIR}/${OUTPUT_NAME}.pot ) add_custom_command( TARGET pot-update PRE_BUILD COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} --from-code=utf-8 --force-po --output=${LOCALE_BUILD_DIR}/${OUTPUT_NAME}.pot --keyword=_ --width=80 ${PO_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "pot-update: Pot file generated: ${LOCALE_BUILD_DIR}/${OUTPUT_NAME}.pot" ) else () message(STATUS "pot-update not created!") endif (GETTEXT_XGETTEXT_EXECUTABLE) if (GETTEXT_MSGMERGE_EXECUTABLE) message(DEBUG " msgmerge: ${GETTEXT_MSGMERGE_EXECUTABLE}") add_custom_target( pot-merge COMMENT "pot-merge: Done." DEPENDS ${LOCALE_BUILD_DIR}/${OUTPUT_NAME}.pot ) file(GLOB PO_FILES ${LOCALE_DIR}/*/${OUTPUT_NAME}.po) message(TRACE " PO_FILES: ${PO_FILES}") foreach(PO_FILE IN ITEMS ${PO_FILES}) message(DEBUG " Adding msgmerge for: ${PO_FILE}") add_custom_command( TARGET pot-merge PRE_BUILD COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} ${PO_FILE} ${LOCALE_BUILD_DIR}/${OUTPUT_NAME}.pot COMMENT "pot-merge: ${PO_FILE}" ) endforeach() else () message(STATUS "pot-merge not created!") endif (GETTEXT_MSGMERGE_EXECUTABLE) if (GETTEXT_MSGFMT_EXECUTABLE) message(DEBUG " msgmerge: ${GETTEXT_MSGFMT_EXECUTABLE}") file(GLOB PO_LANGS LIST_DIRECTORIES true ${LOCALE_DIR}/*) message(TRACE " PO_LANGS: ${PO_LANGS}") add_custom_target( po-compile COMMENT "po-compile: Done." ) foreach(PO_LANG IN ITEMS ${PO_LANGS}) if(IS_DIRECTORY ${PO_LANG}) message(STATUS " Adding msgfmt for: ${PO_LANG}") file(RELATIVE_PATH REL_PO_LANG "${LOCALE_DIR}" "${PO_LANG}") set(MO_BUILD_DIR "${LOCALE_BUILD_DIR}/${REL_PO_LANG}") file(MAKE_DIRECTORY "${MO_BUILD_DIR}") set(MO_NAME "${MO_BUILD_DIR}/${OUTPUT_NAME}.mo") add_custom_command( TARGET po-compile PRE_BUILD COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} --output-file=${MO_NAME} ${OUTPUT_NAME}.po WORKING_DIRECTORY "${PO_LANG}" COMMENT "po-compile: ${PO_LANG}" ) install(FILES "${LOCALE_BUILD_DIR}/${REL_PO_LANG}/${OUTPUT_NAME}.mo" DESTINATION "${LOCALE_INSTALL_DIR}/${REL_PO_LANG}/LC_MESSAGES" COMPONENT Runtime) endif() endforeach() else () message(STATUS "pot-compile not created!") endif (GETTEXT_MSGFMT_EXECUTABLE) |
接下来,修改 Linux 工程的配置文件,增加对 本地化(i18n) 文件的引用,在合适的位置增加如下代码:
|
1 2 3 |
# locale build and install set(FLUTTER_LOCALE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/locale") add_subdirectory(${FLUTTER_LOCALE_DIR}) |
完整的代码参考如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# Project-level configuration. cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "abc") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "xxx.xxx.xxx") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() # Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Define the application target. To change its name, change BINARY_NAME above, # not the value here, or `flutter run` will no longer work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} po-compile) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # locale build and install set(FLUTTER_LOCALE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/locale") add_subdirectory(${FLUTTER_LOCALE_DIR}) # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() |
使用多语言的代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
#include "my_application.h" #include <flutter_linux/flutter_linux.h> #ifdef GDK_WINDOWING_X11 #include <gdk/gdkx.h> #endif #include <sys/stat.h> #include <libgen.h> #include <locale.h> #include <libintl.h> #include "flutter/generated_plugin_registrant.h" #define LOCALE_DIR "/locale/" #define PACKAGE "app" #define _(String) gettext(String) struct _MyApplication { GtkApplication parent_instance; char **dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // 118n static void setup_app_locale() { struct stat sb; const char *proc_name = "/proc/self/exe"; if (lstat(proc_name, &sb) >= 0) { /* Add one to the link size, so that we can determine whether the buffer returned by readlink() was truncated. */ ssize_t bufsiz = sb.st_size + 1; /* Some magic symlinks under (for example) /proc and /sys report 'st_size' as zero. In that case, take PATH_MAX as a "good enough" estimate. */ if (0 == sb.st_size) { bufsiz = PATH_MAX; } ssize_t loc_dir_len = bufsiz + strlen(LOCALE_DIR) + 1; char *buf = (char *)malloc(loc_dir_len); if (NULL != buf) { ssize_t nbytes = readlink(proc_name, buf, bufsiz); if (nbytes >= 0) { /* If the return value was equal to the buffer size, then the the link target was larger than expected (perhaps because the target was changed between the call to lstat() and the call to readlink()). Warn the user that the returned target may have been truncated. */ if (nbytes == bufsiz) { g_message("(Returned buffer may have been truncated)\n"); } if (nbytes <= bufsiz) { buf[nbytes] = '\0'; } char *dir = dirname(buf); strncat(dir, LOCALE_DIR, strlen(LOCALE_DIR)); char *loc = setlocale(LC_ALL, NULL); g_message("current locale is:%s", loc); g_message("locale files dir is:%s", buf); setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, buf); textdomain(PACKAGE); } free(buf); } } } // Implements GApplication::activate. static void my_application_activate(GApplication *application) { MyApplication *self = MY_APPLICATION(application); GtkWindow *window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = TRUE; setup_app_locale(); #ifdef GDK_WINDOWING_X11 GdkScreen *screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, _("app_name")); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, _("app_name")); } gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); FlView *view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status) { MyApplication *self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GObject::dispose. static void my_application_dispose(GObject *object) { MyApplication *self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass *klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication *self) {} MyApplication *my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } |
常见报错
如果命令执行的时候报错:
英文报错信息如下:
1 /usr/bin/msgfmt: input file doesn't contain a header entry with a charset specification中文报错信息如下:
1 /usr/bin/msgfmt: 输入文件不包含指定字符集的文件头项上述错误信息在 ubuntu 25.10系统上出现,之前的版本没有此报错信息。
如果需要修复此问题,则需要在 .po 文件的头部增加语言描述信息,参考如下:
英文添加如下内容:
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Language: en\n"中文添加如下内容:
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Language: zh\n"







