跳转至

Welcome

connect raspberry and arduino via i2c

准备

先接线,然后启动arduino, 最后在树莓派中验证和读写。

连接两个设备的GND SDA SCL

树莓派的SDA连arduino的SDA,SCL接SCL,这跟串口的接线不一样(RX接TX,TX接RX)。

alt text

alt text

启动arduino

arduino 从端代码,注册了i2c的地址为0x08

#include <Wire.h>
#define SLAVE_ADDRESS 0x08
byte data_to_echo = 0;
void setup()
{
  Wire.begin(SLAVE_ADDRESS);
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
}
void loop() { }
void receiveData(int bytecount)
{
  for (int i = 0; i < bytecount; i++) {
    data_to_echo = Wire.read();
  }
}
void sendData()
{
  Wire.write(data_to_echo);
}

树莓派开启i2c

进入树莓派终端执行raspi-config 开启I2c功能,重启后执行命令i2cdetect -y 1,可以看到bus上的设备。

树莓派的master端读写操作代码

#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#define DEVICE_ID 0x08
int main (int argc, char **argv) {
    // Setup I2C communication
    int fd = open("/dev/i2c-1", O_RDWR);
    if (fd == -1) {
        printf("Failed to init I2C communication.\n");
        return -1;
    }
    ioctl (fd, I2C_SLAVE, DEVICE_ID);
    printf("I2C communication successfully setup.\n");
    // Send data to arduino
    uint8_t data_to_send = 17;
    i2c_smbus_write_byte(fd, data_to_send);
    // Read data from arduino
    int received_data = i2c_smbus_read_byte(fd);
    printf("Data received: %d\n", received_data);
    if (received_data == data_to_send) {
       printf("Success!\n");
    }
    close(fd);
    return 0;
}

C的宏定义

宏定义是在c/c++里特有的方式, 像变量一样, 又像模板编程一样, 但最常见的用法还是做头文件的唯一性保证

在每一个头文件都套用这种格式,就可以避免多次引入头文件而导致的重复定义报错

#ifdef FILE_NAME
#def FILE_NAME

// 代码

#endif FILE_NAME

原理

宏定义与变量、模板的最大区别在与处理的时期, 宏定义在预编译时处理, 而变量和模板函数则是在编译期处理。 查看预编译后的代码可以使用命令gcc -E 或者 cpp, 实际上是前者是调用了后者

NAME
       cpp - The C Preprocessor

用法

除了#ifdef 的用法,宏定义可以分两种类型,变量型和函数型

变量型

这个最简单,就像使用变量一样,先define 然后再使用

# marco.c
#define BUFFER_SIZE 1024

int main(){
    foo = (char *) malloc (BUFFER_SIZE);
}
执行 gcc -E marco.c 得到
foo = (char *) malloc (1024);

多行使用 '\' 来连接

#include <stdio.h>
#define GREETING_STR \
  "hello \
world"

  • 注意, 宏定义的定义不分前后, 也不像变量那样先定义再使用, 宏定义可以先使用后定义

以下两种方式的效果相同

#define GREETING_NAME "wayou"

#define GREETING "hello," GREETING_NAME

int main() {
printf(GREETING);
return 0;
}
+#define GREETING "hello," GREETING_NAME

#define GREETING_NAME "wayou"

-#define GREETING "hello," GREETING_NAME

int main() {
printf(GREETING);
return 0;
}

函数型

函数类型的宏,可以像正常函数一样指定入参,入参需为逗号分隔合法的 C 字面量。 宏的参数必须要用括号包起来,否则当参数为表达式时,会出错

#define min(X, Y)  ((X) < (Y) ? (X) : (Y))
  x = min(a, b);            x = ((a) < (b) ? (a) : (b));
  y = min(1, 2);            y = ((1) < (2) ? (1) : (2));
  z = min(a + 28, *p);      z = ((a + 28) < (*p) ? (a + 28) : (*p));
 ```

### 宏定义字符串化

当宏定义的参数被引号包起来时 不会进行替换如下
```c
#define foo(x) x, "x"
foo(bar)         bar, "x"

加入需要将参数替换到字符串里, 可以使用'#'

#define WARN_IF(EXP) \
do { if (EXP) \
        fprintf (stderr, "Warning: " #EXP "\n"); } \
while (0)
WARN_IF (x == 0);
      do { if (x == 0)
           fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0);

而当 这里的x 也是宏定义时, 只有if里的x会替换, 字符串里的x则不会替换

#define X ( 1 - 1 )
WARN_IF ( X == 0);
会被替换为

do { if (( 1 - 1 ) == 0) fprintf (
stderr
, "Warning: " "X == 0" "\n"); } while (0);

拼接

通过 ## 可将两个宏展开成一个,即将两者进行了拼接,宏拼接一般用在需要拼接的宏是来自宏参数的情况,
其他情况,大可直接将两个宏写在一起即可

当有以下情况时非常有用

struct command
{
  char *name;
  void (*function) (void);
};

struct command commands[] =
{
{ "quit", quit_command },
{ "help", help_command },
…
};

可以使用如下:

#define COMMAND(NAME)  { #NAME, NAME ## _command }

struct command commands[] =
{
COMMAND (quit),
COMMAND (help),

};

不定参数和混合参数

宏定义也可以使用不定参数

#define eprintf(args…) fprintf (stderr, args)
// or
#define eprintf(…) fprintf (stderr, __VA_ARGS__)

也可以使用混合参数

#define eprintf(format, args...) fprintf (stderr, format, args)
这个可以常在格式化打印时用到, 例如 spdlog

#define SPDLOG_LOGGER_CALL(logger, level, ...)                                                                                             \
    if (logger->should_log(level))                                                                                                         \
    logger->log(spdlog::source_loc{SPDLOG_FILE_BASENAME(__FILE__), __LINE__, SPDLOG_FUNCTION}, level, __VA_ARGS__)

#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE
#define SPDLOG_LOGGER_TRACE(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::trace, __VA_ARGS__)
#define SPDLOG_TRACE(...) SPDLOG_LOGGER_TRACE(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_TRACE(logger, ...) (void)0
#define SPDLOG_TRACE(...) (void)0
#endif

#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG
#define SPDLOG_LOGGER_DEBUG(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::debug, __VA_ARGS__)
#define SPDLOG_DEBUG(...) SPDLOG_LOGGER_DEBUG(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_DEBUG(logger, ...) (void)0
#define SPDLOG_DEBUG(...) (void)0
#endif

重复和覆盖

这些是相似的:

#define FOUR (2 + 2)
#define FOUR         (2    +    2)
#define FOUR (2 /* two */ + 2)

以下都是不同的宏

#define FOUR (2 + 2)
#define FOUR ( 2+2 ) // 空白位置不一样 
#define FOUR (2 * 2) // 宏的内容不一样
#define FOUR(score,and,seven,years,ago) (2 + 2) // 入参不一样

对于使用了 #undef 注销过的宏,再次定义同名的宏时,要求新定义的宏不与老的相似。

而如果说一个已经存在的宏,并没有注销,重复定义时,如果相似,则新的定义会忽略,如果不相似,编译器会报警告同时使用新定义的宏。这允许在多个文件中定义同一个宏。

最后

可以查看更多内置宏定义

python 包的管理

python 有 sdist 和 wheel 两种方式管理包:

sdist 是在 python setup.py sdist时产生的包,是一个源码压缩包,在安装时需要编译,所以环境依赖make和gcc
wheel 是在python setup.py bdist_wheel是产生的whl 格式包

从命令都可以看出来sdist即source源码包, bdist 即binary二进制包

sdist 由distutils、setuptools 定义和依赖的编译系统, 可以运行任意的代码
wheel 包为编译和安装时提供了简单的接口,里面包含了二进制的包,可以让安装者不需要知道编译体系,依赖wheel

pip install wheel

两种包的打包命令

前提是环境安装了setuptools和wheel, 且编写了setup.py文件如

# setup.py

from setuptools import setup,find_namespace_packages
#import pathlib
#import pkg_resources
#import os
#import sys

#sys.path.insert(0, os.path.join(
#    os.path.dirname(os.path.abspath(__file__)), 'src'))

# 解析文本文件
#with pathlib.Path('requirements.txt').open() as requirements_txt:
#    install_requires = [str(requirement) for requirement in pkg_resources.parse_requirements(requirements_txt) ]

setup(name='myflask',
      version='1.3',
      install_requires=[
            'Bootstrap-Flask==1.4',
            'Flask==1.1.2',
            'Flask-Login==0.5.0',
            'SQLAlchemy==1.3.18',
            'Werkzeug==1.0.1',
            'WTForms==2.3.1'
          ],
      entry_points={
             'console_scripts':[
                   'myflask=wsgi:main'
                   ]
            },
      package_data = {
        '': ['*.html'],
        '': ['*.css'],
        '': ['*.js'],
        '': ['static/*'],
        '': ['templates/*'],
      },
      py_modules=['myflask'],
      packages=find_namespace_packages(),
      zip_safe=False,
      include_package_data=True,
      )
我这里定义了安装模块,myflask,可以被uwsgi 文件引入,方便管理, 同时也加入了很多html的静态文件, 是一个完整的网站

  1. 生成sdist包, 在项目目录执行python setup.py sdist,可以在sdit目录看到tar包

    # myflask > ls dist                                                                                                                                                                                                      
    myflask-1.3.tar.gz
    

  2. 生成wheel包,在项目目录执行python setup.py bdist_wheel,可以在sdit目录看到whl包

    # myflask > ls dist
    myflask-1.3-py3-none-any.whl  myflask-1.3.tar.gz
    

安装

安装命令相同,pip install myflask-1.3.tar.gz pip instal myflask-1.3-py3-none-any.whl。但过程不同

举例安装yarl 的源码包, 源码包需要编译,如果环境没有gcc,就会安装失败

Collecting yarl<2.0,>=1.0 (from aiohttp==3.6.2)
  Downloading yarl-1.6.2.tar.gz (177kB)
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
    Preparing wheel metadata: started
    Preparing wheel metadata: finished with status 'done'
...

  gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/opt/ha/include/python3.8 -c yarl/_quoting_c.c -o build/temp.linux-x86_64-3.8/yarl/_quoting_c.o
  error: command 'gcc' failed with exit status 1
  ----------------------------------------
  ERROR: Failed building wheel for yarl
  Running setup.py clean for yarl
Failed to build yarl

此时,如果使用wheel包就不会出问题,但如果wheel包里面依赖了二进制文件,则需要区分cpu架构和系统了
我的myflask不依赖任何二进制文件,所以是none, 所有cpu和系统都可以安装

myflask-1.3-py3-none-any.whl 
对于yarl不同, 在pypi.org 下载时,需要选择正确的包。或者选择源码包yarl-1.6.2.tar.gz来安装编译

当然如果让pip选择在线安装就不需要考虑, 他会自动帮你寻找对应你系统的版本

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Files for yarl, version 1.6.2
Filename, size  File type   Python version  Upload date     Hashes
yarl-1.6.2-cp36-cp36m-macosx_10_14_x86_64.whl (128.3 kB)    Wheel   cp36    Oct 13, 2020    View
yarl-1.6.2-cp36-cp36m-manylinux1_i686.whl (293.5 kB)    Wheel   cp36    Oct 13, 2020    View
yarl-1.6.2-cp36-cp36m-manylinux2014_aarch64.whl (294.5 kB)  Wheel   cp36    Oct 13, 2020    View

...

yarl-1.6.2.tar.gz (177.5 kB)    Source  None    Oct 13, 2020    View 

前言

之前学汇编发现教材和实际的有出入, 书上写的int, 但是汇编不通过,而gcc 反汇编的结果是调用syscall。
原来这是两种方式调用方式即: int 0x80 和 syscall

除此之外还有一个名词是vdso, 很多elf文件会链接这个vdso库

ldd a.out                                                                                                                                                                                                                   √ 19:03:30 
    linux-vdso.so.1 (0x00007fffb3de0000)
    libc.so.6 => /usr/lib/libc.so.6 (0x00007f5b48d4a000)
    /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f5b48f36000)

词汇说明

int 0x80 即80中断, 是最老的系统函数调用方式 syscall/sysret 是amd64 制定的标准, 也是目前的x86 64位的标准,即amd64 sysenter/syssysexit 是inter制定的x86 64位标准, 目前已被放弃 vdso 是linux内核虚拟出的so, 实现了int 80 和 syscall,调用方式为 vsyscall

系统函数调用路径

系统调用多被封装成库函数提供给应用程序调用,应用程序调用库函数后,由 glibc 库负责进入内核调用系统调用函数。 即用户函数-> glibc -> 系统调用

int 0x80

int 即是interrupt 中断, 0x80是IDT上注册的中断向量, 每个编号对应一个处理函数handle, linux的0x80的handle即是内核,即系统调用。 所以不同的系统设置的0x80的handle可能不同

调用方式:首先是将参数复制到寄存器, 参数包括系统调用编号和传入参数,然后执行 init 0x80 例如,以下的进程退出的系统调用

.data
    s:
        .ascii "hello world\n"
        len = . - s
.text
    .global _start
    _start:

        movl $4, %eax   /* write system call number */
        movl $1, %ebx   /* stdout */
        movl $s, %ecx   /* the data to print */
        movl $len, %edx /* length of the buffer */
        int $0x80

        movl $1, %eax   /* 退出的系统调用编号 */
        movl $0, %ebx   /* exit status */
        int $0x80

vdos

vdos即 linux-vdso.so.1, 几乎很多elf 都会链接这个库,但其实他并不是真实存在的so文件,
而是由内核虚拟的文件,再映射到用户的进程来调用。

vdos 是对以下几个函数的实现,称作快速调用

#define __NR_gettimeofday 96 //0x60
#define __NR_time 201 //0xc9
#define __NR_clock_gettime 228 //0xE4
#define __NR_getcpu 309 //0x135

对比

所以以上总结其实就3种方式, int ,syscall/sysret , vdso

int 0x80 方式很慢,所以出现了syscall 即快速调用

执行区别

在 x86 保护模式中,处理 INT 中断指令时,CPU 首先从中断描述表 IDT 取出对应的门描述符,判断门描述符的种类,然后检查门描述符的级别 DPL 和 INT 指令调用者的级别 CPL,当 CPL<=DPL 也就是说 INT 调用者级别高于描述符指定级别时,才能成功调用,最后再根据描述符的内容,进行压栈、跳转、权限级别提升。内核代码执行完毕之后,调用 IRET 指令返回,IRET 指令恢复用户栈,并跳转会低级别的代码。

其实,在发生系统调用,由 Ring3 进入 Ring0 的这个过程浪费了不少的 CPU 周期,例如,系统调用必然需要由 Ring3 进入 Ring0(由内核调用 INT 指令的方式除外,这多半属于 Hacker 的内核模块所为),权限提升之前和之后的级别是固定的,CPL 肯定是 3,而 INT 80 的 DPL 肯定也是 3,这样 CPU 检查门描述符的 DPL 和调用者的 CPL 就是完全没必要。正是由于如此,Intel x86 CPU 从 PII 300(Family 6,Model 3,Stepping 3)之后,开始支持新的系统调用指令 sysenter/sysexit。sysenter 指令用于由 Ring3 进入 Ring0,SYSEXIT 指令用于由 Ring0 返回 Ring3。由于没有特权级别检查的处理,也没有压栈的操作,所以执行速度比 INT n/IRET 快了不少。

返回的区别

在 Intel 的手册中,还提到了 sysenter/sysexit 和 int n/iret 指令的一个区别,那就是 sysenter/sysexit 指令并不成对,sysenter 指令并不会把 SYSEXIT 所需的返回地址压栈,sysexit 返回的地址并不一定是 sysenter 指令的下一个指令地址。调用 sysenter/sysexit 指令地址的跳转是通过设置一组特殊寄存器实现的。

vdos的局限(syscall)

而"快速系统调用指令"比起中断方式的系统调用方式,还存在一定局限,例如无法在一个系统调用处理过程中再通过"快速系统调用指令"调用别的系统调用。因此,并不一定每个系统调用都需要通过"快速系统调用指令"来实现。比如,对于复杂的系统调用例如 fork,两种系统调用方式的时间差和系统调用本身运行消耗的时间来比,可以忽略不计,此处采取"快速系统调用指令"方式没有什么必要。而真正应该使用"快速系统调用指令"方式的,是那些本身运行时间很短,对时间精确性要求高的系统调用,例如 getuid、gettimeofday 等等。

最后总结

int 是最老的方式,目前用amd64的 syscall 方式, 而vdso是基于syscall实现的快速调用。
只有在调用clock_gettime、gettimeofday、getcpu、time这些系统调用时,才会使用vdso,其他系统调用是通过syscall实现的

目的

解锁华为平板M3的BL锁,以及获取root权限

背景

我有一个华为平板M3,WIFI版, 型号BTV-W09,系统是emui5, 买来没什么用,最大的功能就是看视频。 偶尔发现一个app,LinuxDeploy, 可以在安卓上安装完整的Linux系统,而不是内置的阉割版, 前提是获得root权限。

步骤

全部步骤分4个:
1. 解BL 2. 刷入recovery 也就是RTWP 3. 将root压缩包复制到平板,在RTWP下安装 4. 安装supersu 的apk 5. 可选刷入xposed框架,并安装xposed manager,步骤参考3,4

解锁

因为华为官方停止申请解锁BL的服务, 所以需要上淘宝找人帮你搞定。 解锁BL之后, 在关机状态按住电源和音量减,进入fastboot模式时,有红字提示unlocked

刷入rec

找到与设备型号对应的rec非常重要,因为型号不对会刷不进去,我尝试了很多个版本,最终在华为论坛找到了。 有了rec后, 让手机处于fastboot状态, 连接手机到电脑,使用 fastboot flash recovery rec 来刷入 ,提书刷入成功之后,可能自动重启,如果没有重启,长按电源键强制关机。
关机状态下, 按住电源键和音量+,进入recovery , 能看到RTWP的界面说明输入成功,如果没有看到RTWP,而是进入华为官方的eRecovery 表示失败,又可能是被华为覆盖了。 一旦能进入RTWP, 那么RTWP会自动安装,以后就不用担心被覆盖的问题。

刷入root工具

将root.zip 拷贝到平板的储存卡目录,进入RTWP,点INSTALL, 然后选择root.zip 就会开始安装了, 安装后再安装supersu 的应用

这样就root成功了, 所以步骤很简单,找到对应机型的rec 很关键。

刷入xposed

xposed 很强大,但是xposed只是个框架,需要安装包来实现对app的hack。但是我安装完,没发现什么很强大包,感觉也没什么用。

更新

平板一直都在吃灰,最近发现访问网站都报证书错误,可能是系统旧了(后面发现是电池馈电太久,时间不同步),所以决定升级系统。这次完整贴出命令,因为发现我之前写的难以参考。在XDA网站发现有新的lineage17适合btv-w09,于是按照帖子里面的方法做: 1. 下载TWRP 3.3.1-1,lineage 17 系统压缩包, boot 镜像,我的是wifi-only版本。 2. 在平板关闭状态下,按住电源+音量减,振动的时候释放电源键,然后就进入fastboot状态,显示phone unlock 3. 刷入twrp fastboot flash recovery twrp-xxx.img 4. 刷入boot fastboot flash boot boot-xxx.img 5. 进入twrp,这里有点迷惑,网上说(包括我之前说的)按住电源+音量加总是无法进入twrp而是进入了华为的eRecovery,使用adb boot-recovery则进入了正常系统。最后发现在开发者模式下开启高级重启功能,重启的时候选择重启到recovery则能正常进入Twrp界面。可能是平板接着USB线。 6. 帖子上说了,升级lineage需要Wipe,彻底初始化但保留系统,执行后连sdcard目录也会格式化,剩几个标准目录。 7. 用adb push 将系统安装压缩包发送到平板的存储空间,adb push Lineage-xxx.zip /sdcard,然后在TWRP中选择 Install,选择这个zip 进行安装。 不过发现这个zip文件校验失败,不管他了。

然后就重启进入新版本的Lineage 17(android 10),可惜的是没有相机功能。

2025年重新刷lineageos20

同样问题又出现了

  1. 进入 fastboot 模式 adb reboot bootloader, 如今没有adb reboot fastboot命令了, fastboot模式和bootloader模式应该是一样的
  2. 刷入新的recovery fastboot flash recovery ~/Downloads/recovery.img
  3. 进入recovery,格式化data分区
  4. 在recovery中点击Apply Update, 再点击Apply from ADB, 执行线刷
  5. 在电脑输入adb sideload ~/Downloads/lineage-20.0-20250102-RELEASE-btvw09.zip

但是第5步出现错误

adb sideload ~/Downloads/lineage-20.0-20250102-RELEASE-btvw09.zipmain  
adb: sideload connection failed: insufficient permissions for device: missing udev rules? user is in the plugdev group
See [http://developer.android.com/tools/device.html] for more information
adb: trying pre-KitKat sideload method...
adb: pre-KitKat sideload connection failed: insufficient permissions for device: missing udev rules? user is in the plugdev group
See [http://developer.android.com/tools/device.html] for more information 

查看设备情况提示

adb devices
List of devices attached
SLYDU17401001667        no permissions (missing udev rules? user is in the plugdev group); see [http://developer.android.com/tools/device.html]

网上查找解决办法,尝试了好几个,最终通过

# 添加udev规则
cat /etc/udev/rules.d/51-android.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="id_you_copied", MODE="0666", GROUP="plugdev"

sudo chmod a+r /etc/udev/rules.d/51-android.rules
sudo udevadm control --reload-rules
sudo udevadm trigger

上面还是不行,最后拔掉usb线又重新插入就正常了

背景

公司有台arm64位的设备,安装的银河麒麟系统,经验证就是debian 9。
现在需要运行更多arm64虚拟机。

安装步骤

配置免密sudoer

这里记一个坑, 第一次配置时拼写错误,导致sudo 命令执行失败。需要重置密码,所以很麻烦。
然后推荐使用 sudo visudo 命令来修改sudoer配置文件,这个命令在编辑结束后校验配置文件,避免出现编辑错误导致无法使用sudo的情况。

kylin@Kylin:~$ cat /etc/sudoers.d/kylin 
kylin   ALL=(ALL:ALL) NOPASSWD:ALL

配置软件源

第一个是本地iso, 下面的是中科大的镜像源。 每个都设置了trust免校验gpg

deb [trusted=yes] file:///mnt/iso/ juniper main multiverse restricted universe
deb [trusted=yes] http://ftp.cn.debian.org/debian/ stretch main contrib non-free
deb [trusted=yes] http://ftp.cn.debian.org/debian/ stretch-updates main contrib non-free
deb [trusted=yes] http://mirrors.ustc.edu.cn/debian-security/ stretch/updates main contrib non-free
deb [trusted=yes] http://ftp.cn.debian.org/debian/ stretch-backports main contrib non-free 

安装虚拟工具

安装 qemu, libvirt, virt-manager

sudo apt install  qemu  libvirt virt-manager

libvirt 没有网络

执行virsh net-list -all 发现default 网络处于未激活状态, 于是执行 systemctl status libvirtd 发现

10月 30 15:46:27 Kylin libvirtd[16741]: 2020-10-30 07:46:27.262+0000: 16764: error : virFileReadAll:1420 : 打开文件 '/sys/class/net/virbr0-nic/operstate' 失败: 没有那个文件或目录
10月 30 15:46:27 Kylin libvirtd[16741]: 2020-10-30 07:46:27.262+0000: 16764: error : virNetDevGetLinkInfo:2530 : unable to read: /sys/class/net/virbr0-nic/operstate: 没有那个文件或目录
10月 30 15:46:54 Kylin libvirtd[16741]: 2020-10-30 07:46:54.710+0000: 16745: error : virFirewallApply:916 : 内部错误:Failed to initialize a valid firewall backend
需要安装以下组件,然后重启libvirtd,这是因为libvirt的网络依赖这几个组件来创建nat 网络

sudo apt install ebtables iptables dnsmasq
systemctl restart libvirtd

virt-manager 提示aarch64 安装 uefi 错误

直接安装 qemu-efi

apt install qemu-efi

virt-manager 允许非root用户访问

因为当以普通用户运行 virt-manager 时,qemu的连接指向非qemu:///system, 这时看到的虚拟机和root看到的不一样,所以必须使用sudo运行。 通过修改配置来启用普通用户也访问qemu:///system, 将uri_defualt 的注释去掉。

vim /etc/libvirt/libvirt.conf 

#
# These can be used in cases when no URI is supplied by the application
# (@uri_default also prevents probing of the hypervisor driver).
#
uri_default = "qemu:///system"

这样就可以使用virt-manager来创建虚拟机了,跟x86的使用一样。

顺便记一下vnc的配置, 因为机器在机房里, 通过tigervnc来使用桌面。
另外提示一下,virt-manager 配置好后,可以直接连接远程的virt-manager,非常方便。

使用tigervnc 连接需要在远程主机安装 tigervnc-server 和 tigervnc-password。 使用普通用户执行 tigervnc-password 设置访问密码,然后直接执行tigervnc-server

这时候vnc只接受本地连接, 如果远程访问,要使用ssh 本地转发来实现。 这也是vnc推荐的方式。

现在本地允许本地转发命令, 监听本地端口 9302, 转发到远程的 9300

ssh -L 9302:localhost:9300   -N -f  ssh-kylin

然后执行 vncviewer 127.0.0.1:9302 ,输入密码就能打开远程桌面了。

LVM 功能介绍

LVM 是在硬件和系统之间多加了一层,可以很方便地修改分区和扩容。 从效率上看肯定有所损失的,但是对于企业而言,灵活性也很重要
LVM具有在线扩容和快照的重要功能

在线扩容

当磁盘容量不够或者需要替换磁盘时,就需要扩容, 如果是替换磁盘就再需要换出旧硬盘

扩容步骤

使用lvm中的pvmove 命令假如原来是使用pv 为/dev/sda1, 新的硬盘为/dev/sdb1
1. 新建pv: #pvcreate /dev/sdb1 2. 扩容vg:#vgextend vg1 /dev/sdb1 3. 如果要调整分区大小,使用lvresize -r -S 新大小。 不带-r的话需要重新mount才能生效, -S 是表示最终大小,-s +/- 是调整大小

换出步骤

vgreduce 是将pv踢出vg, pvremove是删除pv标记

  1. 把sda1 上数据迁移到sdb1上: #pvmove /dev/sda1 /dev/sdb1
  2. vgreduce 从vg1踢出sda1盘 #vgreduce vg1 /dev/sda1
  3. pvremove 删除磁盘分区的pv 标记 #pvremove /dev/sda1

磁盘换出是否需要停业务

LVM的在线扩容是不需要停止业务的,但是应该在业务闲的时候做。LVM在做pvmove的时候,会冻结旧分区,新的操作就需要做备份。 pvmove有时候很慢,但是不能中断,否则出现很多遗留的分区。

与pvmove类似的功能的还有磁盘镜像, 磁盘镜像的好处很明显,即使复制失败了,原数据依然存在。

但两个技术都要求能够“原子化”,所以不得不对业务有所影响,而且影响的是磁盘IO, 无法通过降低业务的NICE值来减弱这种影响

LVM 快照功能

当系统是安装在LVM文件系统之上,那么当需要进行高危操作的时候,可以先对分区进行快照备份,避免操作失败又无法恢复。 公司有一台centos7的自用系统, 需要升级到Centos8 以支持 Docker buildx。 虽然因操作到无法运行yum,dnf 时宣布失败,但好在有snapshot备份, 恢复后完好如初,太好用。

步骤

  1. 挂载如下
$ lsblk 
NAME                  MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda                     8:0    0   477G  0 disk  
├─sda1                  8:1    0   200M  0 part  /boot/efi
├─sda2                  8:2    0     1G  0 part  /boot
└─sda3                  8:3    0 475.8G  0 part  
  ├─centos-root       253:2    0    50G  0 lvm   /
  ├─centos-swap       253:4    0  15.7G  0 lvm   [SWAP]
  ├─centos-docker     253:18   0   150G  0 lvm   /var/lib/docker

根分区为50G, 而卷组还剩余160G,完全足够生成一个镜像,50G并未完全使用。

$ sudo vgs
  VG     #PV #LV #SN Attr   VSize   VFree   
  centos   1   4   0 wz--n- 475.74g  160.05g
  data     1  13   0 wz--n-  <7.28t <721.91g

  1. 先对根分区进行快照,注意是根分区

    # lvcreate -s -n <snapshot_name> -L <size> <logical_volume>
    
    $ lvcreate -s -n backup -L 50G  /dev/centos/root
    
    这里指定了快照的大小为50G, 理论上是要比源分区大, 这里也可以直接设置100G, 系统会自动调整大小到合适。

  2. 进行一顿操作后, 进行恢复, 提示根分区正在使用, 重启后生效。

$ lvconvert --mergesnapshot /dev/centos/backup 
  Delaying merge since origin is open.
  Merging of snapshot centos/backup will occur on next activation of centos/root.

保存

如果需要对快照进行备份到其他位置, 可以直接将快照分区挂载,然后通过tarball 来压缩。

tar -cvzf backup.tar.gz /mnt/lv_snapshot

还可以直接通过rsync 将挂载的snapshort 传输到其他服务器上

rsync -aPh /mnt/lv_snapshot  <remote_user>@<destination_server>:<remote_destination>

快照回滚

lvm的snapshort 也是写时复制的(copy on write),所以创建快照操作非常快, 当分区进行删除和修改操作的时候, lvm文件系统会拷贝文件到其他地方保存。

问题

lvm快照不等于备份, 仅有快照是不能恢复的

Linux 中断请求

中断请求的英文是IRQ(Interrupt Request),是用来驱动CPU正常工作的重要机制。 中断根据源头分类成: 由外设发出的硬件中断,软件发出的软中断,以及异常组成。

以前,每个外设都连接一根线到PIC(programmable Interrupt circuit)芯片,有PIC芯片来发生数据到CPU,并将CPU的INTR引脚置位。 现在,CPU集成了PIC成为APIC(Advanced PIC)。

中断分类

中断分为3类型

硬件中断

鼠标和键盘,还有IO设备都会发出硬件中断,例如网卡的数据到了,就会出发中断请求(IRQ), 这个请求会触发CPU去执行ISR, 而这个ISR需要在系统启动的时候注册的。一个Vector表。

软件中断

当播放电影的时候,声音和画面的同步非常重要,这是由系统的定时器jiffies来不停地调度声音播放器来准确播放对应的声音。 软中断在实时操作系统的作用也非常重要。

异常中断

异常中断又分为3类: 缺页异常(Faults),陷入异常(Traps),异常退出(Aborts)

例如当程序的内存被swap到硬盘了或者一段动态链接库还没有载入到内存里来,在程序走到这个位置之前, 程序就会提前发出缺页异常,当系统检查了权限和地址有效后(地址有效表示在逻辑地址范围内), CPU开始为程序执行加载需要的数据,并清除这个异常。 所以这个异常是可以修正的,这个请求必须提前发出。

当GDB调试的时,设置断点会插入一条特殊的指令到程序里,当执行到断点位置就会出发Traps请求,软件的主导权由CPU交给GDB。

当程序触发一个异常例如0/0时,会出发退出异常,由cpu来清理堆栈。

请求的可阻塞性

对于硬件中断请求,CPU的决策是立即执行,对于软中断和异常,CPU采用尽量延后的策略。

查看系统的中断

在中断执行 watch -n1 "cat /proc/interrupts"可以定时更新中断的次数

列的含义从左至右以此为:

 IRQ vector, interrupt count per CPU (0 .. n), the hardware source, the hardware source's channel information, and the name of the device that caused the IRQ.

记录一下上课笔记

  1. 进入nfs,autofs目录卡住,如何退出,应该是进入了D状态
  2. chrome 需要笔记本插件,js控制插件
  3. 修改root密码时,在linux 选项后面添加 rd.break 然后默认进入内核根目录,然后rw重新挂载系统根目录 /sysroot/
  4. fdisk 设置分区类型: 在创建分区结束后,使用t来标记文件类型, 然后使用L查看类型代码,查找到Linux swap 对应的代码是85

RHCIA

修改了网络信息之后,必须使用nmcli connection down和up connection才会生效

nmtui connection down Wired_connection1
nmtui connection up Wired_connection1

配置httpd服务时,修改port后,因为port非标准端口,需要用semanage打tag。否则失败,可以查看journeyctl -e 查看到错误信息和解决办法

semanage port -a -t http_port_t -p tcp 82

创建/home/admins 目录,并设置在目录中创建的文件,其组所有权自动设置为 adminusr。 这里就是设置s位,有数值方法和+-法

chmod 2770 /home/admins

# 或

chmod o-x /home/admins
chmod o-r /home/admins
chmod g+w /home/admins
chmod g+s /home/admins # 这里设置组的s位

验证方法,进入目录创建文件,查看组名

autofs 挂载有两个关键,第一在/etc/auto.master.d/ 目录下创建后缀名为autofs的文件, 第二是编辑挂载配置文件

内容格式

# cat /etc/auto.master.d/indirect
#主挂载点   挂载配置
/internal   /etc/auto.demo

内容配置,* 表示所有目录(根据nfsserver的情况), 和&搭配使用

# cat /etc/auto.demo
# 间接挂载目录    挂载配置    挂载路径(nfs地址)
*   -rw,fstype=nfs4,sync    servera:/shares/indirect/&

setfacl 和getfacl 设置和显示文件属性

设置natash用户读写权限
setfacl u:natasha:rw    /var/tmp/fstab
设置harry用户没有任何权限 
setfacl u:harry:-   /var/tmp/fstab

重置root密码,在linux 行末追加 rd.local, 按ctrl+x 启动

重新挂载系统根目录, chroot到根目录和修改密码

mount -orw,remount /systemroot/
chroot /systemroot/
passwd

逻辑卷扩容

设置分区start位置的时候,输入2048s。 s指扇区,一个扇区为512字节, 2048s指1M,这1M的分区为esp分区

扩容命令lvextrend,这个扩容只是将逻辑卷扩容, 而题目要求对文件系统扩容, 还需要执行文件系统类型对应的扩容命令如resize2fs(ext), xfs_growfs(xfs)。 但对lvextend命令添加 -r 参数可以一步到位

# 扩容到300MB
lvextend -r -L 300MB /dev/new_redhat/lv1

创建逻辑分区, 注意避免磁盘不够用,尽量共用上一题的磁盘。而且创建卷组的时候不要忘记-s参数

题目可能不是直接给分区大小,而是给出扩展块大小和拓展块个数,其实分区大小=拓展块大小 × 拓展块个数。 在lvcreate -l 来指定块个数

这时必须使用msdos分区类型才有主分区和拓展分区,而且为拓展分区分配剩余的所有空间。 最后在拓展分区上创建逻辑分区, 并将创建的逻辑分区加入卷组

创建分区,大小通过上面的算法计算出来

parted 
mkpart
print
exit

创建卷组

vgcreate -s 20MB -n npgroup /dev/vdb2
创建逻辑卷, 注意是小写l
lvcreate -l 45 -n np npgroup
这样就创建了一个 20×45 = 900MB的逻辑卷

创建新的swap分区,注意需要修改fstab, 而且验证的时候,不需要重启系统,而是执行

systemctl daemon-reload 
swapon -a  # 类似mount -a
swapon --show 

vdo --vdoLogicalSize 5G, 不能使用GB

vdo create --name test --vdoLogicalSize 5G --device /dev/vdb
blkid 查到 UUID, 最后添加到fstab

vdo 持久挂载 mount

default,x-systemd.requires=vdo.service

RHCE

ansible的有用命令 ansbile-doc -l | grep 查找模块和介绍 ansible-doc 模块 查看模块具体说明

创建inventory 文件, 子组的写法为

[webservices:children] prod

prod 是webserverices组的子组

创建ansible.cfg 文件,可以复制 /etc/ansbile/ansible.cfg 到~/ansbile/目录下

容易忘记的选项是远程登陆身份, 即配置 remote_user = devops 如果环境没有配置免密登陆,需要在[defaults] 栏添加 ask_pass = True

验证方法 ansible all -m ping

ansbile ad-hoc 添加yum 源 ansible 模块的使用方法 ansible 主机名 -m 模块 -a '参数'

脚本要添加可执行权限

#!/bin/bash

# 先要切换到工作目录
cd /home/devops/ansible/

ansible all -m yum-repository  -a 'name=  description=  baseurl=  enabled= gpgcheck= gpgkey= '

安装软件包

安装软件包如'Development Tools', 需要在包名前面加'@'即 '@Development Tools' 验证yaml 语法格式: ansible-playbook --syntax-check *.yml

- name: install  development tools
  hosts: dev
  tasks:
    - name: install development tools
        yum:
          name: "@Development Tools"
          state: present                  

同步时间依赖角色,安装角色包'rhel-system-roles' 需要在配置文件里添加上roles路径

playbook 示例在/usr/share/ansibe/roles/rhel-system-roles.timesync/Readme.md 现成的yml示例在/usr/share/doc/rhel-system-roles/timesync/example-timesync-playbook.yml

# ansible.cfg
[default]
roles_path = /home/devops/ansible/roles:/usr/share/ansible/roles

使用ansible-galaxy 命令安装角色,安装到本地roles目录

yml文件很简单, 执行命令 ansible-galaxy install -r roles/requirement.yml -p roles/ -p 表示安装路径

- name: balancer
  file: /home/devops/ansible/files/5/haproxy.tar

创建apache角色, 角色对应一系列的任务和模板文件

通过ansible-galaxy init apache 来初始化角色目录,修改角色目录下的tasks/mail.yml 来添加任务,任务所需的模板文件在同级目录templates下
通过ansible servera -m setup 来查询环境变量, 在模板文件中使用ansible_facts['fqdn'] 来引用变量 题目说的hostname 是serverc.lab.example.com 对应变量是fqdn, 另外一个是ip即 ['default_ipv4']['address']

例如,安装httpd用到yum模块, 启用httpd 用到service模块, 防火墙配置用到firewalld模块

# home/devops/ansible/role/apache/tasks/main.yml

---
- name: install httpd
  yum:
    name: httpd
    state: present

- name: enable httpd
  service:
    name: httpd
    state: started
    enabled: yes

- name: firewalld config
  firewalld:
    service: httpd
    immediate: yes
    permanent: yes
    state: enabled

- name: install templates
  template:
    src: index.html.j2
    dest: /var/www/html/index.html 

模块文件放到templates目录下

# home/devops/ansible/role/apache/templates/index.html.j2
Welcome to {{ ansible_facts['fqdn'] }} on {{ ansible_facts['default_ipv4']['address'] }}

最后还需要创建一个playbook, 如同其他playbook 一样执行

# newrole.yml
---
- name: install new role
  hosts: webservers
  roles:
    - apache

执行balancer 角色, 创建一个负载均衡

验证: ssh到serverd 上,curl localhost, 先是访问的serverb, 然后访问的serverc

---
- name: deploy apache on webservers
  hosts: webservers
  roles:
    - apache

- name: deploy balancer on balancer
  hosts: balancers
  roles:
    - balancer

- name: deploy phpinfo on webservers
  hosts: webservers
  roles:
    - phpinfo
可能出现的错误,提示应该使用bind,这是由于balance role的template 错误

修改办法, 将listen行换行并加上bind

# /home/devops/ansible/roles/balancer/templates/haproxy.cfg.j2

backend app
  {% for host in groups.balancers %}
-   listen {{ daemonname }} 0.0.0.0:{{ listenport }}
+   listen {{ daemonname }} 
+   bind 0.0.0.0:{{ listenport }}
  {% endfor %}

创建逻辑卷,考验block/rescue/always, 使用lvol,when 和play对齐

使用了block/rescue/always就不需要tasks关键词

---
- name: create a logic volume with size of 5000 and format
  hosts: all
  block:
    - name: create logic volume
      lvol:
        vg: research
        lv: data
        size: 1500
    - name: fomat data volume
        filesystem:
          fstype: ext4
          dev: /dev/research/data
  rescue:
    - name: debug message
      debug:
        msg: can't create a size of 1800
    - name: create a logical volume with size of 800 and format
        lvol:
          vg: research
          lv: data
          size: 1500
    - name: fomat data volume
        filesystem:
          fstype: ext4
          dev: /dev/research/data
      when: ansible_facts['lvm']['vgs']['research'] is defined
      ignore_errors: yes
    - name: debug 2 
        debug:
          msg: volume group does not exist
      when: ansible_facts['lvm']['vgs']['research'] is undefined

创建hosts文件, 考验jinija 的for循环和读inventory文件

取facts变量的方法参考balancer的模板: /home/ansible/roles/balancer/roles/template/haproxy.cfg.j2

基于给的hosts.j2 文件修改, 拼凑出ip url alias格式,分别对应 default_ipv4.address, fqdn, hostname
ansible_facts在hostvars[host]中

# 取
{% for host in groups.all %}
{{ hostvars[host]['ansible_facts'] *忽露不写了* }} × × 
{% endfor %}
--- 

创建playbook的要小心,题目要求生成所有主机的纪录,所以hosts是all, 但只部署在dev组的主机上要加when,   
要加`when: 'dev' in group_names`

```yml
---
- name: gen hosts and deploy on dev group
  tasks:
    - name: gen hosts and deploy
        template:
          src: hosts.j2
          dest: /etc/myhosts
      when: "'dev' in group_names"

创建web server, 注意将文件标注setype为httpd_system_content_t

通过man semanager-fcontext 查询setype, 或者使用ls -ldZ来查询selinux标签

创建文件,目录、链接都用file模块,但操作文件内容用copy模块 创建组用group模块,创建用户用user模块 容易搞反的链接的src是指向的文件,所以是/webdev, 可以看帮助文档

---
- name: create web server
  hosts: webservers
  tasks:
    - name: add group
        group:
           name: webdev
           state: present
    - name: create directory
        file:
          path: /webdev
          group: webdev
          state: directory
          mode: u+rwx,g+rwxs,o-rx
 -        setype: httpd_system_content_t
    - name: create a symbolic link
        file:
 -        src: /var/www/html/webdev
 -        dest: /webdev
 +        src: /webdev
 +        dest: /var/www/html/webdev
          state: link
    - name: create index.html
        copy:
          content: Development
          dest: /var/www/html/webdev/index.html
+         setype: httpd_system_content_t                    

创建硬件报告,用到loops进行数组循环,内置变量item和 regexs匹配,lineinfile 进行文件编辑

---
- name: generate hw report
  hosts: all
  vars:
    hw_all:
      - hw_name: HOST
        hw_content: "{{ ansible_facts['hostname'] }}"
      - hw_name: BIOS_version
        hw_content: "{{ ansible_facts['bios_version'] }}"

创建加密文件,用到ansible-vault命令

ansible-vault encrypt lock.yml --vault-password-file=secret.txt 

添加用户, ansible-playbook 通过 --vault-password-file=secret.txt 来传密码文件

通过vars_files来读取变量文件, loop 来循环遍历数组变量, 然后模板直接取密码后用'| password_hash('sha512')' 来加密密码 通常情况下在playbook中,开头位置引用一个变量时都需要"{{ variable }}", 但是在when块里,则直接使用 variable。 另外字符串要用“string”

创建用户时, 指定附属组用groups, 主要组是group

- {{ variable }} ## 错误
key: {{ variable }} ## 错误
- "{{ variable}}" ## 正确
key: "{{ variable }}" ## 正确

结果

---
- name: create user on dev,test
  hosts: dev,test
  vars_files:
    - lock.yml
    - user_list.yml
  tasks:
    - name: create group devops
      group:
        name: devops
        state: present
    - name: create user 
      user:
        name: "{{ item.name }}"
-       group: devops
+       groups: devops        
        password: "{{ pw_developer | password_hash('sha512') }}"
      loop: "{{ user }}"
-     when: "{{ item.job }}" == developer
+     when: item.job == "developer"
- name: create user on prod
  hosts: prod
  vars_files:
    - lock.yml
    - users_list.yml
  tasks:
    - name: create group manager
      group:
        name: manager
        state: present
    - name: create user
      user:
        name: "{{ item.name }}"
        groups: manager
        password: "{{ pw_manager | password_hash('sha512') }}"
      loop: "{{ user }}"
-     when: "{{ item.job }}" == manager
+     when: item.job == "manager"

修改密码, 使用ansible-vault rekey 修改密码,或者先解密后加密

修改文件内容, 使用copy模块,以及when条件判断

在playbook的内建变量 inventory_hostname 表示定义在inventory里的主机名称
也可以使用ansible_facts['hostname'],前提是被管理节点的hostname必须如果管理节点定义名称一样。 因为后者是到被管理节点获取。

使用when 限定条件, 满足条件才能执行这个task

---
- name: create issue
  hosts: all
  tasks:
    - name: create issue file
      copy:
        content: "Development"
        dest: /etc/issue
      when: inventory_name in groups.dev  

第二次排错

ansible-galaxy 安装角色, 指定方法分别是 src/name
- src: files/5/balancer.tar
  name: balancer
- src: files/5/phpinfo.tar
  name: phpinfo

timesync 看doc 示例, 验证方法是看timedatectl 看system clock syncronize 为 yes

---
- name: use timesync role
  hosts: all
  vars:
    timesync_ntp_servers:
      - hostname: 
        iburst: yes
  roles:
    - rhel_system_roles.timesync

创建hosts, 要用 {% for host in groups.all %} {{ hostvars[host].ansible_facts }} {% endfor %}

使用firewalld模块时,需要指定immediate 参数,确保能立即生效

设置文件的权限时,应使用u=rwx,g=rwxs,o=rw, 而不是u+rwx,原因很好理解

SQL优先级问题

例如数学中的加减乘除,以及编程里的运算符,在sql里也有优先级,依次是:

FROM clause
WHERE clause
SELECT clause
GROUP BY clause
HAVING clause
ORDER BY clause

优先级依次下降,所以FROM的优先级比WHREE高,这样会有什么影响?

优先级的问题

因为SELECT的优先级比WHERE低,所以在SELECT里面定义的alias别称就不能在WHERE 里面使用
但可以在ORDER里面使用,因为ORDER 后执行

常见问题

...
GROUP BY a, b, c
ORDER BY NULL
. . .
GROUP BY a, b, c
ORDER BY a, b, c

在上面的两个例子里的ORDER BY都不会执行,分别解释:

  1. 第一个例子里的ORDER 是删除GROUP的排序
  2. 第二个例子的ORDER是重复了GROUP已经干了的事情

验证方法

可以找个在线SQL工具,例如 http://sqlfiddle.com/

先执行创建数据库命令

DROP TABLE if exists new_table;

CREATE TABLE `new_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`testdecimal` decimal(6,2) DEFAULT NULL,
PRIMARY KEY (`id`));

INSERT INTO `new_table` (`testdecimal`) VALUES ('1234.45');
INSERT INTO `new_table` (`testdecimal`) VALUES ('1234.45');

set @mysqlorder := '';

select @mysqlorder := CONCAT(@mysqlorder," SELECT ") from new_table,(select @mysqlorder := CONCAT(@mysqlorder," FROM ")) tt
JOIN (select @mysqlorder := CONCAT(@mysqlorder," JOIN1 ")) t on ((select @mysqlorder := CONCAT(@mysqlorder," ON1 ")) or rand() < 1)
JOIN (select @mysqlorder := CONCAT(@mysqlorder," JOIN2 ")) t2 on ((select @mysqlorder := CONCAT(@mysqlorder," ON2 ")) or rand() < 1)
where ((select @mysqlorder := CONCAT(@mysqlorder," WHERE ")) or IF(new_table.testdecimal = 1234.45,true,false))
group by (select @mysqlorder := CONCAT(@mysqlorder," GROUPBY ")),id
having (select @mysqlorder := CONCAT(@mysqlorder," HAVING "))
order by (select @mysqlorder := CONCAT(@mysqlorder," ORDERBY "));

在执行 select @mysqlorder; 结果如下:

@mysqlor`der
(null)

可见其优先级顺序为

FROM JOIN1 JOIN2 WHERE ON2 ON1 ORDERBY GROUPBY SELECT WHERE ON2 ON1 ORDERBY GROUPBY SELECT HAVING HAVING