分类 Linux 下的文章

用Linux也有几个年头了,最长碰见的问题就是,不同的程序对于同一套基础软件有着不同的版本需求,比如Python2/Python3,Java8/Java11,以及各个版本的gcc等等。然而,由于太懒一直没有找到一个好的软件版本管理工具。

可是这次在玩Rock64的时候,被mawk活生生地坑了一把,还顺便学会了update-alternatives这个基本命令= =

起因是Rock64的swap虚拟内存。秉持着内存多大,swap就应该有多大的传统思想,看到我4G内存的Rock64只有1G的swap,心里甚是不爽,于是决定修改配置文件来把swap修改到至少2G以上。

由于编写系统的人不同,初始化系统swap的脚本位置也不同。在Armbian的系统下,使用的swap也是压缩的zram,初始化的脚本位置在/usr/lib/armbian/armbian-zram-config

可以看到其中激活zram作为swap分区的函数activate_zram_swap,截取其中的关键部分如下:

# Load zram module with n instances for swap: one per CPU core, $ZRAM_MAX_DEVICES
# defines the maximum, on modern kernels we overwrite this with 1 and rely on
# max_comp_streams being set to count of CPU cores or $ZRAM_MAX_DEVICES
uname -r | grep -q '^3.' && zram_max_devs=${ZRAM_MAX_DEVICES:=4} || zram_max_devs=1
cpu_cores=$(grep -c '^processor' /proc/cpuinfo | sed 's/^0$/1/')
[[ ${cpu_cores} -gt ${zram_max_devs} ]] && zram_devices=${zram_max_devs} || zram_devices=${cpu_cores}
module_args="$(modinfo zram | awk -F" " '/num_devices/ {print $2}' | cut -f1 -d:)"
[[ -n ${module_args} ]] && modprobe zram ${module_args}=$(( ${zram_devices} + 2 )) || return

# Expose 50% of real memory as swap space by default
zram_percent=${ZRAM_PERCENTAGE:=50}
mem_info=$(LC_ALL=C free -w 2>/dev/null | grep "^Mem" || LC_ALL=C free | grep "^Mem")
memory_total=$(awk '{printf("%d",$2*1024)}' <<<${mem_info})
mem_per_zram_device=$(( ${memory_total} / ${zram_devices} * ${zram_percent} / 100 ))

可以看到,脚本获得系统的内存容量,然后按照预先设置好的比例建立好zram的分区大小。奇怪的是,按照脚本设计,zram大小应该是系统内存的一半实际上只有四分之一。

这是为啥呢?好问题。

在我多次修改swap分区比例,并重启测试之后发现,似乎脚本所识别到的内存容量只占总内存容量的一半。在我手动执行了上面的相关代码后,露出了端倪:awk的数据大小有限制。系统所自带的awk,允许的最大整数为2147483647,使用再大的数输入awk,输出的整数结果都不会超过这个数,而这恰好是一个32位有符号整形的最大值。

在网上冲浪后发现,实际上Debian最小化安装版(听名字应该能发现Armbian是Debian家的吧)中,默认自带的awkmawk,查看版本可以发现这玩意还是1996年11月的,怎么想支持64位数据的可能性都不大= =(ps. 查了一下,amd64指令集的提出是2000年,产品最早出现在2003年)

然后就要感谢大公无私的GNU了。作为awk的后继,GNU组织开发了gawk,在实现awk原有功能的基础上添加了大量插件库(虽然我还没用到过),不必多想,64位数据的问题自然早已解决。

安装完gawk后,实际上系统是把/usr/bin/awk最终重定向到了gawk的可执行文件。根据上文表述可以推测,Debian的非最小化安装版本的默认awk不是mawk,而是gawk。这也是为啥只有在嵌入式设备上才容易发生这类问题的原因了吧= =

为什么说是最终呢?观察/usr/bin/awk的指向可以发现,它并非直接指向/usr/bin/gawk,而是指向了/etc/alternatives/awk。这就引出了本文的后半篇内容,update-alternatives命令。(名字又臭又长的,一度以为是ubuntu用户专属命令

update-alternatives是Linux下的一个标准命令。简单解释来说,就是允许在系统中安装同一个软件包的多个版本,并且根据需求修改默认调用软件包版本的版本。比如在本文前半篇中的场景,一个系统中可能同时安装了mawkgawk,两者的基本功能完全一致,到底使用哪一个awk作为默认的这个工作,就可以通过update-alternatives配置软链接轻松完成。

常用命令:

update-alternatives --get-selections # 查看当前所有存在配置的文件(似乎还可以配置程序库)
update-alternatives --config <name>  # 使用交互方式设置该条目的默认配置;如果留空,对当前系统中存在的所有条目依次进行配置

devtoolset是个好东西,解决了我使用CentOS多年要自行更新gcc工具链的困扰。

然而我为什么使用了多年才知道= =

使用这条命令启用包含有devtoolset的软件源:

sudo yum install centos-release-scl
sudo yum-config-manager --enable rhel-server-rhscl-7-rpms

然后快乐sudo yum install devtoolset-*即可。

横杠后的数字,可选的有7、8和9。根据测试,分别对应的是GCC的7、8和9三个大版本。

然后是启用:

scl enable devtoolset-8 bash

其中8bash可以分别替换为需要的devtoolset的版本,以及要使用的命令行的可执行文件,就可以实现一般的配置方法了。此时再看gcc -v,主版本和devtoolset的版本一致。

https://unix.stackexchange.com/questions/222054/how-can-i-use-linux-as-a-gateway

本来想自己拿开发板做个无线AP用的,结果给实验室修网用上了

就nm离谱,好好的流量转发服务突然就挂掉了= =
学长推测之前配置的时候可能用了iptables以外的第三方软件,但是真假未知。

下面copy三条万金油命令放这,实现内网网口经外网网口联网,其中outer表示外网网口,inner表示内网接口。
好像从-i-o两个参数就能看出来

iptables -t nat -A POSTROUTING -o <outer> -j MASQUERADE

iptables -A FORWARD -i <inner> -o <outer> -j ACCEPT

iptables -A FORWARD -i <outer> -o <inner> -m state --state RELATED,ESTABLISHED -j ACCEPT

===================================
2020/5/28更新

丢人,居然写反了= =
第二条和第三条命令的innerouter是反过来的,表示双向流量相互转发,但是不知道为什么,第三条写反了还是能用

之前看到网上说把^M替换掉就好了,但是文件中一直看不到,搜索后发现vim智能地把行尾有^M的文件按照Windows格式打开了,也就是说此时^M是不被作为可见字符处理的,所以替换时一直说找不到。(:set ff可以看出来)

需要加一步这个,显示地要求按照Unix格式显示文件:

:e ++ff=unix

然后就是常规的:%s/^V^M//g操作了。

https://www.linuxquestions.org/questions/linux-newbie-8/telinit-vs-init-539578/

这里给出结论:从用户的角度来说,是没有任何区别的。

但是如果从实际上出发的话。使用telinit更加容易理解。init本身是一个应该只运行在pid==1情况下的程序,它是Linux系统在启动后执行的第一个进程,它负责一系列的系统初始化工作。因而,“所有其他进程都是init的子进程”的说法是正确的。也就是说,init初始化了哪些服务,系统就会进入哪种状态,这也正是系统运行级别runlevel的原理。

正因为init是一个用于初始化系统的程序,所以它理论上不应该被重复执行,所以从便于用户理解的角度来说,应该给予修改系统运行级别的指令一个单独的命令,它负责告诉(tell)init,用户要求切换至另一种系统运行级别,这就是telinit。实际上,由于init内部在设计的时候就已经考虑到了这种可能性(即runlevel的切换),实际上在init执行任务前会对自身的进程编号pid进行判断,在进程号不为1的时候就执行telinit的功能,即允许通过直接调用init指令来更改系统的运行级别。因而在不少情况下,是允许直接创建一个telinitinit的链接来实现等同功能的——因为在多进程模式下,用户是创建不了pid==1的进程的。