收藏-希腊字母表


收藏以备之后使用

序号 大写小写英文注音国际音标注音中文注音
1 Ααalphaa:lf阿尔法
2Ββbetabet贝塔
3Γ γ gammaga:m伽马
4Δ δ deltadelt 德尔塔
5 Εε epsilonep`silon 伊普西龙
6Ζζ zetazat 截塔
7 Ηη eta eit 艾塔
8 Θθ thetθit 西塔
9Ιι iot aiot 约塔
10Κκ kappakap 卡帕
11λ lambda lambd 兰布达
12Μμ mumju
13Νν nunju
14Ξξ xiksi 克西
15 Οο omicron omik`ron 奥密克戎
16π pi pai
17Ρρ rhorou
18σ sigma `sigma 西格马
19Ττ tau tau
20Υ υ upsilonjup`silon 宇普西龙
21 Φ φ phi fai 佛爱
22 Χ χ chi phai西
23 Ψ ψ psi psai 普西
24 Ωω omega o`miga 欧米伽

U-boot无法使用NFS从Ubuntu中下载文件

最近在调试一块Linux板卡的时候需要在U-boot中挂载Linux镜像,但是发现总是超时挂载失败。

失败时的log输出

因为板卡是可以ping通Ubuntu主机的,所以可以基本可以排除是本地网络的问题。

既然考虑是NFS的问题,那么就需要先确定NFS的配置是否正确

先查看/etc/exports文件中的配置是否正确,确认无误后确定NFS根目录的权限是否正常,这些都确认没有问题后就可以进行下一步的检查了。

首先查看nfs的版本:

sudo cat /proc/fs/nfsd/versions
输出:
-2 +3 +4 +4.1 +4.2

可以看到当前Ubuntu主机没有支持V2,但是U-boot需要使用V2,所以这里需要打开NFS V2的支持。

这里有两种方式打开,如果是Ubuntu18.04的话,需要编辑配置文件的内容。

sudo vim /etc/default/nfs-kernel-server 
# Number of servers to start up
#RPCNFSDCOUNT=8
RPCNFSDCOUNT="-V 2 8"

# Runtime priority of server (see nice(1))
RPCNFSDPRIORITY=0

# Options for rpc.mountd.
# If you have a port-based firewall, you might want to set up
# a fixed port here using the --port option. For more information, 
# see rpc.mountd(8) or http://wiki.debian.org/SecuringNFS
# To disable NFSv4 on the server, specify '--no-nfs-version 4' here
#RPCMOUNTDOPTS="--manage-gids"
RPCMOUNTDOPTS="-V 2 --manage-gids"

# Do you want to start the svcgssd daemon? It is only required for Kerberos
# exports. Valid alternatives are "yes" and "no"; the default is "no".
NEED_SVCGSSD=""

# Options for rpc.svcgssd.
#RPCSVCGSSDOPTS=""
RPCSVCGSSDOPTS="--nfs-version 2,3,4 --debug --syslog"

修改后保存,退出,然后重启NFS服务就可以了。

sudo /etc/init.d/nfs-kernel-server restart

如果是Ubuntu20.04需要修改NFS的配置文件

sudo vim /etc/nfs.conf
#
# This is a general configuration for the
# NFS daemons and tools
#
[general]
pipefs-directory=/run/rpc_pipefs
#
[exports]
# rootdir=/export
#
[exportfs]
# debug=0
#
[gssd]
# verbosity=0
# rpc-verbosity=0
# use-memcache=0
# use-machine-creds=1
# use-gss-proxy=0
# avoid-dns=1
# limit-to-legacy-enctypes=0
# context-timeout=0
# rpc-timeout=5
# keytab-file=/etc/krb5.keytab
# cred-cache-directory=
# preferred-realm=
#
[lockd]
# port=0
# udp-port=0
#
[mountd]
# debug=0
manage-gids=y
# descriptors=0
# port=0
# threads=1
# reverse-lookup=n
# state-directory-path=/var/lib/nfs
# ha-callout=
#
[nfsdcld]
# debug=0
# storagedir=/var/lib/nfs/nfsdcld
#
[nfsdcltrack]
# debug=0
# storagedir=/var/lib/nfs/nfsdcltrack
#
[nfsd]
# debug=0
# threads=8
# host=
# port=0
# grace-time=90
# lease-time=90
udp=y
# tcp=y
vers2=y
# vers3=y
# vers4=y
# vers4.0=y
# vers4.1=y
# vers4.2=y
# rdma=n
# rdma-port=20049
#
[statd]
# debug=0
# port=0
# outgoing-port=0
# name=
# state-directory-path=/var/lib/nfs/statd
# ha-callout=
# no-notify=0
#
[sm-notify]
# debug=0
# force=0
# retry-time=900
# outgoing-port=
# outgoing-addr=
# lift-grace=y
#
[svcgssd]
# principal=

这个文件需要修改两个地方,一个是开启Version2的支持,另一个如果当前NFS不支持UDP也需要开启UDP的支持。是否已经支持UDP可以使用:

$netstat -a | grep "nfs"
#没有支持
tcp        0      0 0.0.0.0:nfs             0.0.0.0:*               LISTEN     
tcp6       0      0 [::]:nfs                [::]:*                  LISTEN  

#支持
tcp        0      0 0.0.0.0:nfs             0.0.0.0:*               LISTEN     
tcp6       0      0 [::]:nfs                [::]:*                  LISTEN     
udp        0      0 0.0.0.0:nfs             0.0.0.0:*                          
udp6       0      0 [::]:nfs                [::]:*    

如果至此依旧不能解决问题,请确认系统防火墙以及杀毒软件是否误杀,可以先都关闭后尝试。

[Zephyr] 01-开发环境搭建

前言:因为需要做一个蓝牙项目,所以接触到了Zephyr操作系统。本文的内容主要来自官方的文档,有部分是自己的理解和笔记。我的开发过程都是在Ubuntu环境下进行的,Zephyr也支持在Windows和macOS环境下开发,但是该系列文章不会涉及Windows和macOS环境下的开发环境搭建以及开发。

免责声明:本文引用的文章、资料版权及所有权均归原作者所有。受限于自己的能力,本文不保证完全没有错误。如果有与官方文档不一致的地方请以官方文档为准!

1、Zephyr简介

Zephyr是专门面向微控制器(MCU)的实时操作系统。经过很多年的发展,现在的Zephyr已经能够很好的支持多种架构的CPU以及很多厂家的芯片,包括大家熟悉的STM32、GD32、NRF、ESP32等系列的芯片,大有成为MCU界的Linux之势。而事实上现在的Zephyr也是由Linux基金会和Wind River Systems Inc(Intel旗下的子公司)共同管理。在Zephyr身上确实也能看到Linux的影子,比如设备树,没错,Zephyr上也有设备树,不过Zephyr的设备树和Linux的设备树不能说有所不同,只能说完全不同。受限于MCU平台的资源,Zephyr的设备树不像Linux中会有单独的文件和设备树系统。而是在编译阶段发挥作用,在预编译阶段脚本会把.dts中的内容以宏定义的形式生成.h文件。

不过我觉得与其说Zephyr是一个嵌入式操作系统,不如说它是一套开发工具,因为除了操作系统内核Zephyr还包含了文件系统、驱动模型、电源管理、固件更新、密码学等等,Zephyr甚至还包含了蓝牙协议栈、USB协议栈、LVGL等等。

除了上面说的这些优势,最重要的是Zephyr是完全开源的,并且Zephyr 使用Apache 2.0 许可证,也就是说Zephyr是支持商用的。

更详细的介绍大家可以去看Zephyr官方文档中的介绍

2、安装

Zephyr的开发环境搭建相比Keil、MDK、IAR等大家熟悉的IDE那种无脑下一步的安装方式还是要麻烦不少,不管是什么平台都没法逃过命令行,不过大家也不用一看到命令行就发懵,其实常用的命令就那么几个,很快就可以熟悉的。

下面就介绍在Linux下的安装过程,这里我用Ubuntu系统,Zephyr还支持其它的Linux发行版,其它发行版请参照Zephyr官方文档。注意:Zephyr需要Ubuntu 18.04 LTS 及更高的版本!整个安装过程会涉及到很从外网服务器下载源码以及安装包的过程,需要多次尝试或者想别的办法解决。推荐使用Ubuntu20.04或以上版本,会容易一些。

更新软件

Linux在搭建一个环境之前我们一般都会先同步系统上的软件包列表为最新的状态并且更新本地的软件为最新的状态。

sudo apt update
sudo apt upgrade

安装依赖

工具版本
CMake3.20.0
Python3.8
Devicetree compiler1.4.6
Zephyr需要的主要软件依赖以及版本

当Ubuntu版本小于22.04时(实测20.04也不需要,官方文档说大于22.04不需要),需要添加额外的存储库来满足上面的主要依赖的最低版本需求,这里需要使用 Kitware 存档脚本将 Kitware APT 存储库添加到源列表中。

wget https://apt.kitware.com/kitware-archive.sh 
sudo bash kitware-archive.sh

如果是22.04版本就不需要上面这步。其余版本执行完脚本后就可以使用apt安装需要的依赖

sudo apt install --no-install-recommends git cmake ninja-build gperf \
  ccache dfu-util device-tree-compiler wget \
  python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \
  make gcc gcc-multilib g++-multilib libsdl2-dev libmagic1

安装完成后检查一下主要依赖的版本和上面表格中的版本,如果一致就进行下一步,如果不一致就需要手动调整版本。具体的解决方式参考官方安装Linux主机依赖项

cmake --version
python3 --version
dtc --version

获取Zephyr并且安装Python依赖项

Zephyr的安装、使用都依赖于west工具,west工具依赖Python环境,用过Python的朋友一定知道我们平时做Python环境的时候一般都不会全局配置,因为如果全局配置各个软件的兼容性会让你怀疑人生。所以安装Zephyr的时候我们同样使用Python的虚拟环境安装,之后使用的时候再激活环境就可以了,虽然听着好像麻烦了一点,但是如果全局安装遇到兼容性的问题的时候就知道虚拟环境有多美好了。

首先安装Python的venv包,然后创建虚拟环境并且激活环境:

sudo apt install python3-venv
# 在Home下创建了一个名叫zephyrproject的文件夹,后续Zephyr相关的内容都会放在这个文件夹下。这个文件夹的名字和路径可以自定义,但是后面涉及路径的就需要改为你自己的实际路径。
cd ~
mkdir zephyrproject
python3 -m venv ~/zephyrproject/.venv
source ~/zephyrproject/.venv/bin/activate
# 想要停用环境可以用
# source ~/zephyrproject/.venv/bin/deactivate

通过上面的步骤就安装并且激活了Python的虚拟环境,下面我们就在刚才激活的环境中安装west,并且获取Zephyr源代码:

pip install west
west init ~/zephyrproject
cd ~/zephyrproject
west update

成功获取Zephyr的源代码后就可以导出Zephyr Cmake包(让Cmake能够自动加载编译Zephyr应用程序所需的样板代码)并且安装Zephyr额外的依赖:

west zephyr-export
pip install -r ~/zephyrproject/zephyr/scripts/requirements.txt

安装Zephyr SDK

经过上面的步骤,我们已经安装了Zephyr的开发工具west,并且获取Zephyr的源码到我们的电脑中,想要将代码编译为各个实际的可执行文件还需要对应架构的工具链。Zephyr将各个平台的工具链都包含在了Zephyr SDK中,我们只需要安装Zephyr SDK就可以支持各个平台的编译了。

#下载并校验SDK包
cd ~
wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.15.0/zephyr-sdk-0.15.0_linux-x86_64.tar.gz
wget -O - https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.15.0/sha256.sum | shasum --check --ignore-missing

#解压SDK包,Zephyr推荐了几个解压路径,这里我直接解压到home目录下
tar xvf zephyr-sdk-0.15.0_linux-x86_64.tar.gz

#进入解压后的文件夹,安装Zephyr SDK
cd zephyr-sdk-0.15.0
./setup.sh

#安装udev规则这样就可以以普通用户的身份刷写大多数 Zephyr 板
sudo cp ~/zephyr-sdk-0.15.0/sysroots/x86_64-pokysdk-linux/usr/share/openocd/contrib/60-openocd.rules /etc/udev/rules.d
sudo udevadm control --reload

编译测试

至此Zephyr的开发环境就搭建完成了,现在可以尝试编译一下例程验证一下我们的环境搭建是否成功。

这里我使用NRF52840DK开发板,尝试运行LED闪烁的代码。

cd ~/zephyrproject/zephyr
west build -p always -b nrf52840dk_nrf52840 samples/basic/blinky
#下载
west flash

如果环境搭建成功,那么就会看到与我类似的编译输出。

编译完成后输出的内存占用情况

简单解释一下上面的west指令的含义,build代表这是编译命令,-p always表示编译前始终清除之前的编译内容,强制开始完整的编译过程-p 还可以跟auto ,表示自动判断是否需要清除之前的编译结果,如果不加-p指令,如果需要将当前的代码编译为别的板子上运行的代码就会报错。后面的-b是指当前要编译的板子类型,官方支持的板子都在zephyr->board目录下,我们常用的比如STM32,GD32,NRF52840这些都是arm32位的,都在arm目录下。ESP32在xtensa目录下,安装完成之后最好先自己翻着看看各个目录下都有些什么东西。

west flash 可以直接将编译后的代码烧写到板子中,但是在实际情况中经出会遇到比如编译是在统一的云服务器中,或者主机没办法连接调试器,这就需要我们将文件从服务器copy到本地用对应的烧写工具烧写。这里推荐一个叫做NetDrive2的软件,可以很方便的将服务器的文件夹映射成为本地磁盘,当然还有很多别的方式,这里用自己最熟悉的就可以。

至此,环境的安装,验证都完成了,看似整个安装过程很复杂其实归根结底只有三个步骤:

  • 准备环境,依赖、python
  • 安装west,获取源码
  • 安装SDK

安装过程就写到这里,下一篇文章将会讲解如何创建自定义的板级描述文件,并且创建自己的工程。

树莓派镜像打包

众所周知,树莓派是用来折腾的,但是因为系统跑在SD上以及折腾时手残总是无法避免,这就导致系统很容易崩了,系统蹦了如果从头烧镜像,装环境那简直是生不如死,所以这就体现了备份镜像的必要性。

备份镜像最傻瓜的方式就是在Windows平台下使用[Win32 Disk Image]的Read功能直接把SD卡中的镜像读取到文件中,但是直接读取的文件是与SD卡大小一致的,这就导致不管是备份存储还是重新写入都会有很多麻烦,为了解决这个麻烦就需要调整SD卡的分区大小然后再读出备份。而调整分区的操作在Windows下又会遇到一些问题。而且Linux下有十分便利的脚本可以实现镜像压缩,所以推荐的方式是在Linux平台下操作,比如ubuntu。

具体操作:

先将SD连接到Linux主机,并查看设备名以及分区信息。

xingyong@xingyong-dell-7050:~$ sudo fdisk -l

Disk /dev/sda: 931.5 GiB, 1000204886016 bytes, 1953525168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/nvme0n1: 238.5 GiB, 256060514304 bytes, 500118192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x6a4965ff

Device         Boot Start       End   Sectors   Size Id Type
/dev/nvme0n1p1 *     2048 500117503 500115456 238.5G 83 Linux


Disk /dev/sdb: 59.5 GiB, 63864569856 bytes, 124735488 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xe4b7df54

Device     Boot  Start       End   Sectors  Size Id Type
/dev/sdb1         8192    532479    524288  256M  c W95 FAT32 (LBA)
/dev/sdb2       532480 124735487 124203008 59.2G 83 Linux

此时可以看到SD卡的设备名称,我的是sdb,有两个分区,然后使用dd指令将SD中的镜像拷贝到Linux主机内,注意,此时拷贝的依旧是完整的镜像,大小与SD卡大小一致,这就需要你的主机得有比SD卡容量大的空闲空间,如果不够,就需要先调整SD卡分区大小再操作!

xingyong@xingyong-dell-7050:~$ sudo dd if=/dev/sdb of=./raspberrypi.img bs=512
124735488+0 records in
124735488+0 records out
63864569856 bytes (64 GB, 59 GiB) copied, 779.038 s, 82.0 MB/s

经过上面的操作,我们就将镜像从SD卡打包到img文件中了,但是此时的文件大小还是很大,需要压缩一下,这里推荐一个超级好用的脚本[GitHub链接],关于脚本的详细信息大家可以去仓库查看,这里就不展开了。

xingyong@xingyong-dell-7050:~$ sudo pishrink.sh raspberrypi.img 
pishrink.sh v0.1.2
pishrink.sh: Gathering data ...
Creating new /etc/rc.local
pishrink.sh: Checking filesystem ...
rootfs: 120206/3784416 files (0.4% non-contiguous), 1326501/15525376 blocks
resize2fs 1.44.1 (24-Mar-2018)
pishrink.sh: Shrinking filesystem ...
resize2fs 1.44.1 (24-Mar-2018)
Resizing the filesystem on /dev/loop0 to 1660441 (4k) blocks.
Begin pass 2 (max = 14804)
Relocating blocks             XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Begin pass 3 (max = 474)
Scanning inode table          XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
The filesystem on /dev/loop0 is now 1660441 (4k) blocks long.

pishrink.sh: Shrinking image ...
pishrink.sh: Shrunk imgname.img from 60G to 6.6G ...

可以看到执行完脚本之后镜像从60G变为了6.6G,这样你就可以愉快的使用这个打包并压缩的镜像了。

如果要在Linux下烧录镜像依旧可以使用dd指令,只需要调换上面if/of的内容就好。

xingyong@xingyong-dell-7050:~$ sudo dd if=./raspberrypi.img of=/dev/sdb bs=512
13816009+0 records in
13816009+0 records out
7073796608 bytes (7.1 GB, 6.6 GiB) copied, 873.813 s, 8.1 MB/s


把做好镜像的TF卡插回树莓派上启动,进入系统后使用raspi-config对文件系统扩展使用全部TF卡空间就可以愉快的使用了。

如果不知道如何扩展可以查看树莓派手册Expand Filesystem章节

Arduino与树莓派字节对齐问题记录

我在做一个小东西的时候用到了Arduino和树莓派,因为需要将数据采集端放的比较远所以没有直接将传感器接在树莓派上,而是选择了使用Arduino采集传感器数据,然后使用串口与树莓派通信。就在我很快写完两端的代码之后遇到了一个奇怪的问题,那就是明明数据接收解析过程没有问题,但是解析的数据怎么也不对。经过分析发现是两个平台数据长度和默认的对齐方式不同导致的,特此记录一下。

struct sensor_data{
    uint16_t hand;
    double ambient_temp;
    double object_temp;
    uint16_t tail;
}sensor_data;

我在两个平台定义了相同的结构体用来传输数据,在Arduino上将传感器数据获取到之后写入结构体,然后使用按字节从串口发送,因为两个平台都是小端模式,所以理论上在树莓派上我只需要按字节把接收的数据拷贝到相同结构体就可以获取到数据,但是,实际发现Arduino只发送了14字节,也就是说在Arduino平台下double只占用了4字节,与float是一致的。而且是按2字节对齐。

A5 5A D0 A3 E2 41 30 5C E1 41 0F F0

实际打印的数据也印证了我的猜想,后来发现Arduino的文档中关于double的描述明确指出了这个问题。

文档中关于double的说明

树莓派自然是不存在double与float长度一致的问题,而且树莓派是4字节对齐,所以就会出现我遇到的数据错误的问题。知道问题所在,修改代码就愉快的解决问题了。

关于C语言中的字节对齐一般有两种方式:

一、强制按字节对齐:

#pragma pack (n)    //C编译器将按照n个字节对齐。
#pragma pack ()     //取消自定义字节对齐方式。

二、对齐到n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐:

__attribute((aligned (n)))  //C编译器将按照n个字节对齐。
__attribute ((packed))      //取消自定义字节对齐方式。

最后附上此次测试的代码:

树莓派:

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <wiringPi.h>
#include <wiringSerial.h>
 
#pragma pack (2)
struct sensor_data{
    uint16_t hand;
    uint16_t serial_number;
    float ambient_temp;
    float object_temp;
    uint16_t tail;
}sensor_data;
#pragma pack ()
 
struct sensor_data rx_data;

int main(void)
{
    int hs1;
    char uart_rx_buffer[64];
    uint16_t uart_flag = 0;
    int16_t index = 0;
    int16_t data_flag = 0;
    wiringPiSetup();                            // 使用wiring编码去初始化GPIO序号
    // hs1 = serialOpen("/dev/ttyS0", 115200);     // 打开 /dev/ttyS0 串口设备,波特率115200
    hs1 = serialOpen("/dev/ttyUSB0", 115200); // 打开 /dev/ttyUSB0 串口设备,波特率115200
    if(hs1 < 0){
        printf("UART open error %d!\r\n",hs1);
        return -1;
    }
    else{
        printf("UART open success %d!\r\n",hs1);
    }

    char keyboard_input[10] = {};
    while(TRUE){

        int buffer_size = serialDataAvail(hs1);
        while(buffer_size){
            char c = serialGetchar(hs1);    // 从接收缓存区读取一个字节
            if((uart_flag == 0)&&(c == 0xA5)){
                uart_flag = 1;
            }
            else if((uart_flag == 1)&&(c == 0x5A)){
                uart_flag = 2;
            }
            else if((uart_flag == 1)&&(c != 0x5A)){
                uart_flag = 0;
                index = 0;
                memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
            }

            if(uart_flag > 0){
                uart_rx_buffer[index++] = c;
                if(index > sizeof(rx_data)+1){
                    uart_flag = 0;
                    index = 0;
                    memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
                }
            }
            
            if((uart_flag == 2)&&(c == 0x0F)){
                uart_flag = 3;
            }
            else if((uart_flag == 3)&&(c == 0xF0)){
                data_flag = 1;
                memcpy((&rx_data),uart_rx_buffer,sizeof(rx_data));
                uart_flag = 0;
                index = 0;
                memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
            }
            else if((uart_flag == 3)&&(c != 0xF0)){
                uart_flag = 0;
                index = 0;
                memset(uart_rx_buffer,0,sizeof(uart_rx_buffer));
            }
            buffer_size --;
        }

        if(data_flag){
            printf("%5d Ambient = %5.2f   Object = %5.2f\r\n",\
                  rx_data.serial_number,\
                  rx_data.ambient_temp,\
                  rx_data.object_temp);
            data_flag = 0;
        }
    }
 
    serialClose(hs1);                           // 关闭串口
    return 0;
}

因为使用到了wiringPi的库,所以编译的时候记得加-lwiringPi链接,执行的时候也需要使用管理员权限才可执行。

Arduino:

#include <Wire.h>
#include <Adafruit_MLX90614.h>

Adafruit_MLX90614 mlx = Adafruit_MLX90614();

struct sensor_data{
  uint16_t hand;
  uint16_t serial_number;
  float ambient_temp;
  float object_temp;
  uint16_t tail;
}sensor_data;

struct sensor_data tx_data;
uint16_t num =0;

void setup() {
  tx_data.hand = 0x5aa5;
  tx_data.tail = 0xf00f;
  Serial.begin(115200);
  mlx.begin();  
}

void loop() {
  tx_data.serial_number = num;
  tx_data.ambient_temp = mlx.readAmbientTempC();
  tx_data.object_temp = mlx.readObjectTempC();
  Serial.write((char*)(&tx_data),sizeof(tx_data));
  num++;
  delay(500);
}

Tmux 使用教程

Tmux 是一款终端复用命令行工具,一般用于 Terminal 的窗口管理。Tmux 拥有如下特性:

  • 可以同时开启多个会话和窗口,并持久地保存工作状态。

例如,若您需要在 Terminal 中编辑一个文件,同时还需要在 Python 交互环境中执行命令,那么正常情况下您需要开启两个 Terminal。
若您使用 Tmux,则无须开启多个 Terminal,您可使用 session 管理会话和窗口,在多个会话和窗口之间进行切换。

  • 断线后任务能够在后台继续执行。

Tmux 还能解决由于断线导致的任务丢失问题。一般的 shell 工具遇到断线,远程任务可能会中止并无法继续,重连后任务需从头再来。而在 Tmux 中运行的命令,会一直保存在服务器上,断线后只需从 Tmux 中恢复该会话,任务仍然在运行。

Tmux安装

在 Terminal 中使用如下命令:

sudo apt update
sudo apt install tmux

Tmux 由如下三个基础组成

  • 1. Session。即会话,任务通常在 session 中运行,在断开连接后 session 仍会保持。
  • 2. Window。即窗口,一个会话可以包含多个窗口。可以存在多个窗口。
  • 3. Pane。即窗格,一个窗口可以包含多个窗格。类似于 Vim 中 C-w +v 后的效果。

使用 Tmux 管理会话

在Terminal中输入:

tmux

这样就会开启了一个 session-name 为 0 的 Tmux 会话。

tmux会话

左下角即当前窗口的 session-name,此时就可以在这个会话中正常输入命令。

在tmux的会话中启动一个服务

此时如果您和服务器断开连接,tmux 中的任务还会继续保持。您可重新打开 Terminal 后,输入命令:

tmux a -t 0 

其中 0 为之前会话的 session-name。

如果您想从该会话中退出,可以输入如下命令回到普通的 Termina:

tmux detach

此时可以再次输入 tmux 命令开启一个新的会话。Tmux 默认的 session-name 会逐次加一,再次新建的会话默认 session-name 就是 1 了。

在启用会话时可以指定session的名称,方便自己记忆:

tmux new -s [session-name]

eg:
tmux new -s tcpserver
自定义名称的 session

普通 Terminal 页面中,可以查看所有的 Tmux 会话:

tmux ls

如果要删除指定会话,在普通 Terminal 页面中,输入命令:

tmux kill-session -t [session-name]

eg:
tmux kill-session -t 0     #删除名称为0的会话
tmux kill-server           #删除所有会话

切换会话:

tmux switch -t <session-name>

重命名会话:

tmux rename-session -t 0 <new-name>

窗口管理

在每个 session 会话中,您可以开启多个窗口和面板。
Tmux 为了防止与全局快捷键冲突,大部分快捷键需要先需要输入前缀,默认为 Ctrl + b。该操作被定义为 Prefix
 
创建一个窗口需要两步:
  • 第一步:按 Ctrl+B 组合键,然后松开。
  • 第二步:再单独按一下 c 键。
创建了三个窗口

选择窗口

星号(*)在这里表示的是“当前处于活跃状态的窗口”,也就是哪个窗口现在处于可操作状态,星号(*)就在哪个窗口的后面

Tmux 常用快捷键
快捷键	说明	
Prefix ?	显示快捷键帮助	
Prefix :	进入命令模式	
Prefix C-z	挂起会话,不影响其他命令的运行,C 表示 Ctrl 键	
Prefix C-o	调换窗格位置	
Prefix 空格键	采用下一个内置布局	
Prefix !	把当前窗格(pane)变为新窗口(window)	
Prefix "	横向分隔窗格	
Prefix %	纵向分隔窗格	
Prefix q	显示分隔窗格的编号	
Prefix o	跳到下一个分隔窗格	
Prefix 上下键	上一个及下一个分隔窗格	
Prefix C-方向键	调整分隔窗格大小,C 表示 Ctrl 键	
Prefix z	最大化当前窗格,再一次则恢复	
Prefix c	创建新窗口	
Prefix 0~9	选择几号窗口	
Prefix n	选择下一个窗口	
Prefix p	选择前一个窗口	
Prefix l	切换到前一个窗口,该快捷键通常会被重定义为符合 vim 下的空格切换	
Prefix w	以菜单方式显示及选择窗口	
Prefix s	以菜单方式显示和选择会话	
Prefix t	显示时钟	
Prefix ;	切换到最后一个使用的面板	
Prefix x	关闭面板	
Prefix &	关闭窗口	
Prefix d	退出 Tmux,并保存当前会话,此时 Tmux 仍在后台运行,可以通过 Tmux attach 进入指定的会话

Git常用指令

本文并不是Git的使用教程!!!因为自己总是记不住Git的一些常用指令,所以写了本文作为一个笔记,方便自己随时查阅。

一、Git 基本概念

git 中的文件可以存在三个地方,分别是工作区、暂存区和版本库。

  1. 工作区:就是你在电脑里能看到的目录。
  2. 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
  3. 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

二、git config

#全局配置用户名及邮箱,这个配置会应用到所有的Git仓库,在Commit的时候会带上这个信息
$ git config --global user.name "XingyongGuo"
$ git config --global user.email "xingyong.guo@outlook.com"

#设置Git默认使用的文本编辑器, 一般可能会是 Vi 或者 Vim。如果你有其他偏好,比如 vscode 的话,可以重新设置
$ git config --global core.editor code

#设置Git差异分析工具
$ git config --global merge.tool vimdiff

#查看Git配置信息
git config --list

#使用vscode作为difftool
$ git config --global core.editor "code --wait"
$ git config --global -e
#添加
[diff]    tool = default-difftool[difftool "default-difftool"]    cmd = code --wait --diff $LOCAL $REMOTEn 

三、基本操作 init/clone/add/commit

git 最基本的用法就是创建一个仓库 -> 添加文件到暂存区 -> 从暂存区提交到版本库。
创建一个仓库可以使用本地init从本地的文件夹创建,或者clone你需要的仓库到本地,然后在仓库下进行正常的开发,当到达某个节点,比如想临时做一个什么尝试,又想保留当前的状态,但是当前状态又不是那么重要的时候,你就可以把文件提交到暂存区,但是如果当前版本还是比较重要,那还是commit一次吧。

######使用本地文件夹创建仓库######
git init                #在当前文件夹下创建git仓库
git init newrepo        #在指定文件夹下创建git仓库

#########clone已有的仓库###########      
git clone <repo>                        #clone指定的仓库
git clone <repo> <directory>            #clone指定的仓库并命名为<directory>
git clone <repo> --recurse-submodules   #clone指定的仓库并clone仓库的子模块

#########将文件添加到暂存区#########
git add [file1] [file2] ...          #添加一个或多个文件到暂存区
git add [dir]                        #添加指定目录到暂存区,包括子目录
git add .                            #添加当前目录下的所有文件到暂存区
git add -A                           #添加所有改变的已跟踪文件和未跟踪文件
git add -u                           #只更新已经跟踪的文件

######提交暂存区到本地仓库中########
git commit -m [message]              #提交暂存区到本地仓库中
git commit [file1] [file2] ... -m [message]     #提交暂存区指定文件到本地仓库中
git commit -a                        #等于 Add + Commit

四、status/diff/log/

git status 是用来查看当前仓库下的文件状态的,可以看到文件是否已经添加到暂存区以及添加到暂存区后有没有再次修改文件。想查看文件的改动就需要使用diff命令。

git status
git status -s    #查看简短的信息
#######
A:  已经添加到暂存区
AM: 这个文件在我们将它添加到缓存之后又有改动。

*********************************************************************

git diff             #尚未缓存的改动
git diff --cached    #查看已缓存的改动
git diff HEAD        #查看已缓存的与未缓存的所有改动
git diff --stat      #显示摘要而非整个 diff

#显示暂存区和工作区的差异
git diff [file]      

#显示暂存区和上一次提交(commit)的差异
git diff --cached [file]
或
git diff --staged [file]  

#显示两次提交之间的差异
git diff [first-branch]...[second-branch]

*********************************************************************

# --oneline 选项来查看历史记录的简洁的版本
git log --oneline

# --graph 选项,查看历史中什么时候出现了分支、合并
git log --oneline --graph

#查看完整log,包含分支结构 
git log --all --decorate  --graph --oneline

# --reverse 参数来逆向显示所有日志
git log --reverse --oneline

#--author只想查找指定用户的提交日志 (查看5条)
git log --author=XingyongGuo --oneline -5

#指定日期,可以执行几个选项:--since 和 --before,但是你也可以用 --until 和 --after
#查看 Git 项目中三周前且在四月十八日之后的所有提交,我可以执行这个(--no-merges 选项以隐藏合并提交)
git log --oneline --before={3.weeks.ago} --after={2010-04-18} --no-merges

#查看指定文件的修改记录
git blame <file>

五、checkout

这个命令有两种用法,第一种是切换分支,第二种是撤销修改

checkout 本意是检出的意思,也就是将某次commit的状态检出到工作区;所以它的过程是先将HEAD指向某个分支的最近一次commit,然后从commit恢复index,最后从index恢复工作区。

切换分支

 #切换到指定分支
git checkout branchname   

#创建并切换到新的分支 = git branch newbranch + git checkout newbranch
git checkout -b branchname 

#切换分支的时候如果暂存区有没有提交的变动,Git则会阻止切换分支,如果需要丢弃未提交的变动可以加 -f 强制切换。但是如果想保留变动则可以先使用reset将文件从暂存区移除,然后再切换,或者stash当前暂存区的文件。

放弃修改

1、只放弃工作区的改动,index 保持不变,其实就是从当前 index 恢复 工作区:

#放弃工作区中全部的修改
git checkout .
#放弃工作区中某个文件的修改 先使用 git status 列出文件
git checkout -- filename

2、强制放弃 index 和 工作区 的改动:
git checkout -f
这是不可逆的操作,会直接覆盖

六、reset

当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。

 #--mixed 为默认,可以不用带该参数,用于重置暂存区的文件与上一次的提交(commit)保持一致,工作区文件内容保持不变。
git reset [--soft | --mixed | --hard] [HEAD]   

git reset HEAD^            # 回退所有内容到上一个版本  
git reset HEAD^ hello.php  # 回退 hello.php 文件的版本到上一个版本  
git reset 052e             # 回退到指定版本

#--soft 参数用于回退到某个版本
git reset --soft HEAD~3 # 回退上上上一个版本

#--hard 撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除之前的所有信息提交
git reset –hard HEAD~3              # 回退上上上一个版本  
git reset –hard bae128              # 回退到某个版本回退点之前的所有信息。 
git reset --hard origin/master      # 将本地的状态回退到和远程的一样 


HEAD 说明:
    HEAD 表示当前版本
    HEAD^ 上一个版本
    HEAD^^ 上上一个版本
    HEAD^^^ 上上上一个版本
以此类推...

可以使用 ~数字 表示
    HEAD~0 表示当前版本
    HEAD~1 上一个版本
    HEAD^2 上上一个版本
    HEAD^3 上上上一个版本
以此类推...

七、stash

在工作过程中,一定会遇到临时需要切换到别的分支做一些工作的时候,如果此时有未提交的文件但是又不想在此时提交的话就需要暂存当前的文件。

将本地没提交的内容进行缓存并从当前分支移除,缓存的数据结构为堆栈,先进后出。当前工作区没有被追踪的文件是不会被暂存的,所以需要暂存的话需要先add到暂存区。
git stash                    #缓存
git stash save               #缓存
git stash save "*****"       #缓存时加上自己的注解
git stash list               #返回缓存的列表
git stash pop                #将堆栈中最新的内容pop出来应用到当前分支上,且会删除堆中的记录
git stash pop stash@{$num}   #从缓存堆栈中取出指定的一次缓存
git stash apply              #与pop相似,但他不会在堆栈中删除这条缓存,适合在多个分支中进行缓存应用
git stash apply stash@{$num}
git stash drop stash@{$num}  #删除单个缓存
git stash clear              #全清
git stash show stash@{$num}  #显示与当前分支差异加上-p可以看详细差异
git stash branch             #使用缓存创建分支

八、mv/rm

#git mv 命令用于移动或重命名一个文件、目录或软连接

#如果新文件名已经存在,但还是要重命名它,可以使用 -f 参数
git mv -f [file] [newfile]

*****************************************************************************

#将文件从暂存区和工作区中删除
git rm <file>

#如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f
git rm -f <file>

#仅是从跟踪清单中删除(暂存区域移除)
git rm --cached <file>

#递归删除
git rm –r * 

九、remote/pull/push/fetch

#显示所有远程仓库
git remote -v

#显示某个远程仓库的信息
git remote show [remote]

#添加远程版本库
git remote add [shortname] [url]

# 删除远程仓库
git remote rm name  

# 修改仓库名
git remote rename old_name new_name  

*********************************************************************

git pull <远程主机名> <远程分支名>:<本地分支名>

#将远程主机 origin 的 master 分支拉取过来,与本地的 brantest 分支合并
git pull origin master:brantest
or:
git fetch origin master
git checkout brantest
git merge origin/master

#如果远程分支是与当前分支合并,则冒号后面的部分可以省略
git pull origin master

*********************************************************************

git push <远程主机名> <本地分支名>:<远程分支名>
#如果本地分支名与远程分支名相同,则可以省略冒号
git push <远程主机名> <本地分支名>

#如果本地版本与远程版本有差异,但又要强制推送可以使用 --force 参数
git push --force origin master

#删除主机的分支可以使用 --delete 参数,以下命令表示删除 origin 主机的 master 分支
git push origin --delete master

*********************************************************************

#Fetch仓库中所有分支。同时也会下载指定远端的所有commits和文件。
git fetch <remote>

#Fetch 指定分支
git fetch <remote> <branch>

#fetch所有已注册过的远端仓库的全部分支
git fetch --all

十、branch/merge/cherry-pick

#列出分支
git branch

#创建分支命令
git branch (branchname)

#切换分支命令
git checkout (branchname)

#创建新分支并立即切换到该分支下
git checkout -b (branchname) 

#删除分支
git branch -d (branchname)

#合并分支命令
git merge (branchname) 
如果可以使用Fast forward merge模式,Git将会默认使用该模式
git merge --no-ff 不使用快速合并,合并后提交到新的commit

#将某一次提交的修改同步到当前分支并commit到一个新的节点
git cherry-pick <commitHash>

#将某一次提交的修改同步到当前的工作区和暂存区,但是不提交
git cherry-pick -n <commitHash>

#在导入修改commit之前修改提交信息
git cherry-pick -e <commitHash>

#可以直接使用分支名代替<commitHash>导入分支中最新的一次提交
git cherry-pick <branch-name>

#导入多个提交
git cherry-pick <HashA> <HashB>        #将 A 和 B 两个提交应用到当前分支。这会在当前分支生成两个对应的新提交
git cherry-pick A..B                   #转移从 A 到 B 的所有提交,不包括A
git cherry-pick A^..B                  #转移从 A 到 B 的所有提交,包括A

#代码冲突
#解决冲突后
git add .
git cherry-pick --continue
#放弃合并,回到合并之前
git cherry-pick --abort
#退出合并,不回到合并之前
git cherry-pick --quit

#转移另一个代码库的提交,需要先将该库加为远程仓库
git remote add target git://gitUrl     #添加了一个远程仓库target
git fetch target                       #将远程代码抓取到本地
git log target/master                  #检查一下要从远程仓库转移的提交,获取它的哈希值
git cherry-pick <commitHash>           #使用git cherry-pick命令转移提交

git cherry-pick命令的常用配置项如下:
(1)-e,--edit
打开外部编辑器,编辑提交信息。
(2)-n,--no-commit
只更新工作区和暂存区,不产生新的提交。
(3)-x
在提交信息的末尾追加一行(cherry picked from commit ...),方便以后查到这个提交是如何产生的。
(4)-s,--signoff
在提交信息的末尾追加一行操作者的签名,表示是谁进行了这个操作。
(5)-m parent-number,--mainline parent-number
如果原始提交是一个合并节点,来自于两个分支的合并,那么 Cherry pick 默认将失败,因为它不知道应该采用哪个分支的代码变动。
-m配置项告诉 Git,应该采用哪个分支的变动。它的参数parent-number是一个从1开始的整数,代表原始提交的父分支编号。
 $ git cherry-pick -m 1 <commitHash>
上面命令表示,Cherry pick 采用提交commitHash来自编号1的父分支的变动。
一般来说,1号父分支是接受变动的分支(the branch being merged into),2号父分支是作为变动来源的分支(the branch being merged from)。

十一、submodule

git clone <repository> --recursive 递归的方式克隆整个项目
git submodule add <repository> <path> 添加子模块
git submodule init 初始化子模块
git submodule update 更新子模块
git submodule foreach git pull 拉取所有子模块