Bash Scripting

简介

记录一下bash脚本编写的东东,可以参考cheatsheet

调试打印

在Bash脚本里打开打印,并利用shellprof./shellprof ./testcase.sh计算时间。

1
2
3
4
5
6
7
8
9
#!/bin/bash

# 使用 trap 来捕获 DEBUG 信号,每次执行命令前触发
trap 'echo "$BASH_COMMAND"' DEBUG

# 设置调试模式打印前缀PS4,包含时间戳和行号
export PS4='+ $(date "+%Y-%m-%d %H:%M:%S.%3N")\011 '

set -x # 开启调试模式

基础知识

参数

  • $? 是上一个指令执行的状态值
  • $@ 代表shell脚本的命令行参数. $1 , $2 , etc., 分别代表命令行传入的第一个和第二个参数。举例,对于命令./run.sh XXX YYY,$1代表XXX , $2代表YYY

#匹配并删除最短前缀

1
2
3
4
5
6
7
8
9
10
str="/path/to/foo.cpp"
echo "${str%.cpp}" # /path/to/foo
echo "${str%.cpp}.o" # /path/to/foo.o
echo "${str%/*}" # /path/to

echo "${str##*.}" # cpp (extension)
echo "${str##*/}" # foo.cpp (basepath)

echo "${str#*/}" # path/to/foo.cpp
echo "${str##*/}" # foo.cpp

在 Bash 脚本中,### 符号用于字符串操作,具体用于删除前缀部分:

  1. ${str#pattern}:删除变量 str 中最短匹配 pattern 的前缀。
  2. ${str##pattern}:删除变量 str 中最长匹配 pattern 的前缀。

例如:

  1. echo "${str##*.}":这行代码使用 ## 删除了最后一个 . 及其前面的所有内容,从而只保留了文件扩展名 cpp。这是因为 * 匹配所有字符。
  2. echo "${str##*/}":这行代码使用 ## 删除了最后一个 / 及其前面的所有内容,因此只保留了文件名 foo.cpp
  3. ${str#*/} 删除了第一个 / 之前的部分,结果是 path/to/foo.cpp
  4. ${str##*/} 删除了最后一个 / 之前的所有部分,结果是 foo.cpp

这两个符号用于简化路径处理和提取文件名等。

变量作用域

  • Bash 中的变量是 全局变量,也就是说,在脚本中的任何地方都可以访问到这个变量,除非使用 local 关键字将其作用域限制在特定的函数中。
  • local 声明的变量确实限制了它的作用域,但作用域仅限制在当前函数及其子函数(嵌套函数)内。对于嵌套函数,还是共用(读取和修改)上层的local变量。

函数传参

  • Bash里的函数传参和脚本的命令行参数也是共用$@$1等,但是默认是函数的局部变量。
  • 如果忘记使用局部变量,这常会导致一个问题:
    • 外层的for循环的i被内层函数的for循环i修改导致循环异常。
    • 解决方案:需要将内层函数的for循环i定义为local,来与上层的隔离。

其他

  • /dev/null是一个几乎不管向它写入什么,都只返回成功,但是什么都没真的写入的文件。换句话说就是个“无底洞”,扔进去的东西肯定算扔进去了,但是扔进去就看不见了。

Environment

1
2
3
4
5
6
7
8
9
10
11
# read
SCRIPT_DIR="$(dirname $0)"

# set
export HOSTNAME="$(hostname)"

# check Environment
if [[ -z "${ITHEMAL_HOME}" ]]; then
echo "ITHEMAL_HOME environment variable must be set!"
exit 1
fi

实例

判断docker是否启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function container_id() {
sudo docker ps -q --filter 'name=ithemal$'
}

CONTAINER="$(container_id)"

if [[ -z "${CONTAINER}" ]]; then
read -p "Container is not currently running. Would you like to start it? (y/n) " -r

if [[ !($REPLY =~ ^[Yy]) ]]; then
echo "Not starting."
exit 1
fi

# others
fi

尝试获取sudo权限

1
2
3
4
5
6
function get_sudo() {
if ! sudo -S true < /dev/null 2> /dev/null; then
echo "sudo access required for docker:"
sudo true
fi
}

重连或者创建一个到名称为$1tmux窗口

1
2
3
4
5
6
7
8
9
#!/usr/bin/env bash

SESSION=$(tmux ls -F '#S #{session_attached}' | grep ' 0$' | head -n 1 | awk '{$NF=""; print $0}' | awk '{$1=$1;print}')

if [ ! -z "${SESSION}" ]; then
tmux attach -t "${SESSION}"
else
tmux new "bash -l"
fi

参考文献

pip

pip + 换源 ,能使用export http_proxy (:多试几次

pip install xxx -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host=mirrors.aliyun.com 或者设置文件

也可以通过环境变量

1
2
export PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
export PIP_TRUSTED_HOST=mirrors.aliyun.com
Read more

Rust

简介

Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。

Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。

安装

异常简单,默认安装在自己.local/bin下,会自动修改bashrc/zshrc
On Linux and macOS systems, this is done as follows:

1
curl https://sh.rustup.rs -sSf | sh

基础语法

printf

1
2
3
4
5
6
7
impl ClassName {
pub fn printFunc() {
let a = 12;
println!("a is {0}, a again is {0}", a);
//println 不是一个函数,而是一个宏规则。所以有感叹号
}
}

变量

Rust 是强类型语言,但具有自动判断变量类型的能力。

1
2
3
4
5
6
7
8
9
10
11
//可以指定类型
let a: u64 = 123;
//不可变变量
let a = 123;
let a = 456; //不是复制是,重新绑定
let s2 = s1.clone(); //这才是真复制
//变量
let mut a = 123;
a = 456;
//常量
const a: i32 = 123;

函数

函数返回值

Rust 函数声明返回值类型的方式:在参数声明之后用 -> 来声明函数返回值的类型(不是 : )。

不写return是将最后一个当作返回值?(貌似是

Rust是如何实现内存安全的呢?

内存安全

  1. buffer overflow
  2. null pointer dereference
  3. use after free
  4. use of uninitialized memory
  5. illegal free (of an already-freed pointer, or a non-malloced pointer)

所有权

所有权对大多数开发者而言是一个新颖的概念,它是 Rust 语言为高效使用内存而设计的语法机制。所有权概念是为了让 Rust 在编译阶段更有效地分析内存资源的有用性以实现内存管理而诞生的概念。

所有权三规则

  1. Rust 中的每个值都有一个变量,称为其所有者。
  2. 一次只能有一个所有者。
  3. 当所有者不在程序运行范围时,该值将被删除。

原理

如果我们定义了一个变量并给它赋予一个值,这个变量的值存在于内存中。这种情况很普遍。但如果我们需要储存的数据长度不确定(比如用户输入的一串字符串),我们就无法在定义时明确数据长度,也就无法在编译阶段令程序分配固定长度的内存空间供数据储存使用。(有人说分配尽可能大的空间可以解决问题,但这个方法很不文明)。这就需要提供一种在程序运行时程序自己申请使用内存的机制——堆。本章所讲的所有”内存资源”都指的是堆所占用的内存空间。

有分配就有释放,程序不能一直占用某个内存资源。因此决定资源是否浪费的关键因素就是资源有没有及时的释放。

我们把字符串样例程序用 C 语言等价编写:

1
2
3
4
5
{
char *s = (char *)malloc(sizeof(char)*10);
s = "nhooo"; //伪代码了
free(s); // 释放 s 资源
}

很显然,Rust 中没有调用 free 函数来释放字符串 s 的资源(假设 “nhooo” 在堆中,这里)。Rust 之所以没有明示释放的步骤是因为在变量范围结束的时候,Rust 编译器自动添加了调用释放资源函数的步骤

这种机制看似很简单了:它不过是帮助程序员在适当的地方添加了一个释放资源的函数调用而已。但这种简单的机制可以有效地解决一个史上最令程序员头疼的编程问题。

https://hashrust.com/blog/memory-safey-in-rust-part-1/

https://deathking.github.io/2020/08/03/blue-team-rust-what-is-memory-safety-really/

https://segmentfault.com/a/1190000041151698

https://bbs.huaweicloud.com/blogs/193974

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

ML Optimizer

导言

  • 优化算法(Optimizer)目标是优化(最小化或最大化)一个损失函数,以调整模型参数,使模型在训练数据上表现得更好。
  • 在深度学习中,优化算法是训练神经网络时至关重要的组成部分,它们决定了模型参数如何更新以最小化损失。
  • 所以梯度下降、动量法、随机梯度下降、RMSprop、Adam、AdamW、LAMB等算法都是优化算法。
Read more

Safe File Transport

use OpenSSL to encrypt file by RSA key

已有文件

  1. 未加密文件 secretfile.txt
  2. 接受方的公钥 recipients-key.pub

产生一次性加密解密对称密钥secret.key

256 bit (32 byte) random key

1
openssl rand -out secret.key 32

使用secret.key 加密所需文件

生成加密后文件secretfile.txt.enc

1
openssl aes-256-cbc -in secretfile.txt -out secretfile.txt.enc -pass file:secret.key

使用公钥recipients-key.pub加密secret.key

得到加密过的一次性key : secret.key.enc

1
openssl rsautl -encrypt -oaep -pubin -inkey <(ssh-keygen -e -f recipients-key.pub -m PKCS8) -in secret.key -out secret.key.enc

<()是子进程的意思。

1
rm secret.key

发送加密文件

加密文件 secretfile.txt.enc和 secret.key.enc

use OpenSSL to descrypt file

已有文件

  1. 加密文件 secretfile.txt.enc和 secret.key.enc
  2. 接受方的私钥

解密secret.key.enc

1
openssl rsautl -decrypt -oaep -inkey ~/.ssh/id_rsa -in secret.key.enc -out secret.key

解密secretfile.txt.enc

1
openssl aes-256-cbc -d -in secretfile.txt.enc -out secretfile.txt -pass file:secret.key

需要进一步的研究学习

  1. 使用 publickey.pem 可以看这篇 https://blog.csdn.net/makenothing/article/details/54645578
  2. id_rsa.pub.pem产生可以看这个 https://www.czeskis.com/random/openssl-encrypt-file.html

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

https://www.bjornjohansen.com/encrypt-file-using-ssh-key

https://blog.csdn.net/makenothing/article/details/54645578

Nvidia Arch : Ampere & Hopper & Pascal

基本概念

GPU Processing Clusters (GPCs),

Texture Processing Clusters (TPCs),

Streaming Multiprocessors (SMs)

  • CUDA cores: basic integer/floating point arithmetic – high throughput, low latency
  • Load/Store (LD/ST): issues memory accesses to appropriate controller – possibly high latency
  • Special Function Unit (SFU): trigonometric math functions, etc – reduced throughput
  • special tensor cores (Since Turing and Volta): have specialized matrix arithmetic capabilities

H100

GH100

img

上面两张图组成一个SM,Special Function Units (SFUs)

P40

GP102



图中红框是一个SM

A100

GA100

RTX 3090

10496个流处理器,核心加速频率1.70GHz,384-bit 24GB GDDR6X显存。

GA102

在之前的GA100大核心中,每组SM是64个INT32单元、64个FP32单元及32个FP64单元组成的,但在GA102核心中,FP64单元大幅减少,增加了RT Core,Tensor Core也略微减少。

游戏卡与专业卡的区别

  1. 应用方面不同
    1. 游戏卡会对三维图像处理有特殊处理,有光线追踪单元
    2. 专业计算卡,可能对某些格式的解压压缩有特殊单元,或者对半精度计算有特殊支持。
  2. 做工不同
    1. 专业卡由于在服务器上24小时不同工作,在多相供电,散热都堆料处理,游戏卡不同(公版,非公版肯定不一样)
  3. 驱动不同
    1. 游戏卡对应游戏软件的优化驱动,专业卡有对专业软件的驱动支持
  4. 价格不同
    1. 专业卡贵4倍不止。
  5. 参数的不同,对于同一颗核心(以RTX3090与A100 40G举例)
    1. A100的GA100是8块完整的,GA102是7块。
    2. A100领先的地方
      1. 堆料完爆对手
      2. 显存往往更多,AI应用
      3. 访存更快
      4. 支持 High bandwidth memory (HBM)
      5. 在多精度和半精度有优势(NVIDIA A100 SXM4 40 GB VS.NVIDIA GeForce RTX 3090)
    3. RTX3090领先的地方
      1. 频率更高
      2. 有视频输出接口,支持OpenGL,DirectX
      3. 有RT core 光追

参考文献

https://zhuanlan.zhihu.com/p/394352476

Nvidia

Nvidia 的系列产品的基本参数

Read more

OpenMP

线程绑定

OpenMP 4.0 提供 OMP_PLACESOMP_PROC_BIND 环境变量来指定程序中的 OpenMP 线程如何绑定到处理器。这两个环境变量通常结合使用。OMP_PLACES 用于指定线程将绑定到的计算机位置(硬件线程、核心或插槽)。OMP_PROC_BIND 用于指定绑定策略(线程关联性策略),这项策略指定如何将线程分配到位置。

除了 OMP_PLACESOMP_PROC_BIND 这两个环境变量外,OpenMP 4.0 还提供可在 parallel 指令中使用的 proc_bind 子句。proc_bind 子句用于指定如何将执行并行区域的线程组绑定到处理器。

SlURM MPI OpenMP绑定方法参考清华的文档

1
2
3
4
OMP_NUM_THREADS=28 OMP_PROC_BIND=true OMP_PLACES=cores:每个线程绑定到一个 core,使用默认的分布(线程 n 绑定到 core n);
OMP_NUM_THREADS=2 OMP_PROC_BIND=true OMP_PLACES=sockets:每个线程绑定到一个 socket;
OMP_NUM_THREADS=4 OMP_PROC_BIND=close OMP_PLACES=cores:每个线程绑定到一个 core,线程在 socket 上连续分布(分别绑定到 core 0,1,2,3;
OMP_NUM_THREADS=4 OMP_PROC_BIND=spread OMP_PLACES=cores:每个线程绑定到一个 core,线程在 socket 上尽量散开分布(分别绑定到 core 0,7,14,21;
1
2
3
lscpu结合htop观察
NUMA 节点0 CPU: 0-15,32-47
NUMA 节点1 CPU: 16-31,48-63

编译制导格式

静态扩展

  • 文本代码在一个编译制导语句之后,被封装到一个结构块中

孤立语句

  • 一个OpenMP的编译制导语句不依赖于其它的语句

parallel

并行域中的代码被所有的线程执行

for

for语句指定紧随它的循环语句必须由线程组并行执行;

sections

sections编译制导语句指定内部的代码被划分给线程组中的各线程

不同的section由不同的线程执行


single

single编译制导语句指定内部代码只有线程组中的一个线程执行。

线程组中没有执行single语句的线程会一直等待代码块的结束,使用nowait子句除外

来自 https://ppc.cs.aalto.fi/ch3/nowait/

组合parallel for / parallel sections 编译制导语句

  1. Parallel for编译制导语句表明一个并行域包含一个独立的for语句
  2. parallel sections编译制导语句表明一个并行域包含单独的一个sections语句

同步结构

  1. master 制导语句
    1. 指定代码段只有主线程执行
  2. critical制导语句
    1. critical制导语句表明域中的代码一次只能执行一个线程,其他线程被阻塞在临界区
    2. 语句格式:#pragma omp critical [name] newline
  3. barrier制导语句
    1. 同步一个线程组中所有的线程,先到达的线程在此阻塞,等待其他线程
  4. atomic制导语句
    1. 指定特定的存储单元将被原子更新
    2. #pragma omp atomic
      x++;
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      5. flush制导语句
      1. 标识一个同步点,用以确保所有的线程看到一致的存储器视图
      2. ![](https://pic.shaojiemike.top/img/20220108093740.png)
      6. ordered制导语句
      1. 相对于critical,多了一个顺序
      2. 只能出现在for或者parallel for语句的动态范围中
      7. threadprivate语句使一个全局文件作用域的变量在并行域内变成每个线程私有
      1. 每个线程对该变量复制一份私有拷贝


      ### critical vs atomic
      The fastest way is neither critical nor atomic. Approximately, addition with critical section is 200 times more expensive than simple addition, atomic addition is 25 times more expensive then simple addition.(**maybe no so much expensive**, the atomic operation will have a few cycle overhead (synchronizing a cache line) on the cost of roughly a cycle. A critical section incurs **the cost of a lock**.)

      The fastest option (not always applicable) is to give each thread its own counter and make reduce operation when you need total sum.

      ### critical vs ordered
      omp critical is for mutual exclusion(互斥), omp ordered refers to a specific loop and ensures that the region **executes sequentually in the order of loop iterations**. Therefore omp ordered is stronger than omp critical, but also only makes sense within a loop.

      omp ordered has some other clauses, such as simd to enforce the use of a single SIMD lane only. You can also specify dependencies manually with the depend clause.

      Note: Both omp critical and omp ordered regions have an implicit memory flush at the entry and the exit.

      ### ordered example
      vector<int> v;

#pragma omp parallel for ordered schedule(dynamic, anyChunkSizeGreaterThan1)
for (int i = 0; i < n; ++i){



#pragma omp ordered
v.push_back(i);
}

1
2
3
4
5
6
```
tid List of Timeline
iterations
0 0,1,2 ==o==o==o
1 3,4,5 ==.......o==o==o
2 6,7,8 ==..............o==o==o

= shows that the thread is executing code in parallel. o is when the thread is executing the ordered region. . is the thread being idle, waiting for its turn to execute the ordered region.

With schedule(static,1) the following would happen:

1
2
3
4
5
tid  List of     Timeline
iterations
0 0,3,6 ==o==o==o
1 1,4,7 ==.o==o==o
2 2,5,8 ==..o==o==o

语句绑定与语句嵌套规则


Clauses 子句

https://docs.microsoft.com/en-us/cpp/parallel/openmp/reference/openmp-clauses?view=msvc-160

1
2
3
4
5
#pragma omp parallel for collapse(2)
for( int y = y1; y < y2; y++ )
{
for( int x = x1; x < x2; x++ )
{

schedule

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
------------------------------------------------
| static | static | dynamic | dynamic | guided |
| 1 | 5 | 1 | 5 | |
------------------------------------------------
| 0 | 0 | 0 | 2 | 1 |
| 1 | 0 | 3 | 2 | 1 |
| 2 | 0 | 3 | 2 | 1 |
| 3 | 0 | 3 | 2 | 1 |
| 0 | 0 | 2 | 2 | 1 |
| 1 | 1 | 2 | 3 | 3 |
| 2 | 1 | 2 | 3 | 3 |
| 3 | 1 | 0 | 3 | 3 |
| 0 | 1 | 0 | 3 | 3 |
| 1 | 1 | 0 | 3 | 2 |
| 2 | 2 | 1 | 0 | 2 |
| 3 | 2 | 1 | 0 | 2 |
| 0 | 2 | 1 | 0 | 3 |
| 1 | 2 | 2 | 0 | 3 |
| 2 | 2 | 2 | 0 | 0 |
| 3 | 3 | 2 | 1 | 0 |
| 0 | 3 | 3 | 1 | 1 |
| 1 | 3 | 3 | 1 | 1 |
| 2 | 3 | 3 | 1 | 1 |
| 3 | 3 | 0 | 1 | 3 |
------------------------------------------------

private vs firstprivate vs lastprivate

private variables are not initialised, i.e. they start with random values like any other local automatic variable

firstprivate initial the value as the before value.

lastprivate save the value to the after region. 这个last的意思不是实际最后运行的一个线程,而是调度发射队列的最后一个线程。从另一个角度上说,如果你保存的值来自随机一个线程,这也是没有意义的。
firstprivate and lastprivate are just special cases of private

1
2
3
4
5
6
7
#pragma omp parallel
{
#pragma omp for lastprivate(i)
for (i=0; i<n-1; i++)
a[i] = b[i] + b[i+1];
}
a[i]=b[i];

private vs threadprivate

A private variable is local to a region and will most of the time be placed on the stack. The lifetime of the variable’s privacy is the duration defined of the data scoping clause. Every thread (including the master thread) makes a private copy of the original variable (the new variable is no longer storage-associated with the original variable).

A threadprivate variable on the other hand will be most likely placed in the heap or in the thread local storage (that can be seen as a global memory local to a thread). A threadprivate variable persist across regions (depending on some restrictions). The master thread uses the original variable, all other threads make a private copy of the original variable (the master variable is still storage-associated with the original variable).

task 指令

可以指定某一task任务在指定第几个thread运行吗?

section 命令 与 for 命令的区别

简单理解sections其实是for的展开形式,适合于少量的“任务”,并且适合于没有迭代关系的“任务”。每一个section被一个线程去执行。

常用函数

1
2
3
omp_get_thread_num() //获取线程的num,即ID。在并行区域外,获取的是master线程的ID,即为0。
omp_get_num_threads/omp_set_num_threads() //设置/获取线程数量,用于覆盖OMP_NUM_THREADS环境变量的设置。omp_set_num_threads在串行区域调用才会有效,omp_get_num_threads获取当前线程组的线程数量,一般在并行区域调用,在串行区域调用返回为1。
omp_get_max_threads() //返回OpenMP当前环境下能创建线程的最大数量。

环境变量

1
2
3
4
OMP_SCHEDULE:只能用到for,parallel for中。它的值就是处理器中循环的次数
OMP_NUM_THREADS:定义执行中最大的线程数
OMP_DYNAMIC:通过设定变量值TRUE或FALSE,来确定是否动态设定并行域执行的线程数
OMP_NESTED:确定是否可以并行嵌套

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#include <omp.h>

int main(int argc, _TCHAR* argv[])
{
printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());
omp_set_num_threads(5);
printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

#pragma omp parallel num_threads(5)
{
// omp_set_num_threads(6); // Do not call it in parallel region
printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());
}

printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

omp_set_num_threads(6);
printf("ID: %d, Max threads: %d, Num threads: %d \n",omp_get_thread_num(), omp_get_max_threads(), omp_get_num_threads());

return 0;
}

OpenMP和pthread是常见的模型

♦OpenMP为循环级并行提供了方便的功能。线程由编译器根据用户指令创建和管理。

♦pthread提供了更复杂、更动态的方法。线程由用户显式创建和管理。

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

对子句和制导的关系不清楚

参考文献

https://blog.csdn.net/gengshenghong/article/details/7004594

https://docs.microsoft.com/en-us/cpp/parallel/openmp/reference/openmp-clauses?view=msvc-160

VNC

常用命令

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
# shaojiemike @ node5 in ~ [11:26:56]
$ vncserver -list

TigerVNC server sessions:

X DISPLAY # RFB PORT # PROCESS ID
:1 5901 148718 (stale)

# shaojiemike @ node5 in ~ [11:29:39]
$ vncpasswd
Password:

# shaojiemike @ node5 in ~ [11:34:08]
$ vncserver -kill :1
Killing Xtigervnc process ID 148718... which was already dead
Cleaning stale pidfile '/home/shaojiemike/.vnc/node5:1.pid'!

# shaojiemike @ node5 in ~ [11:36:15]
$ vncserver

New 'node5:2 (shaojiemike)' desktop at :2 on machine node5

Starting applications specified in /etc/X11/Xvnc-session
Log file is /home/shaojiemike/.vnc/node5:2.log

Use xtigervncviewer -SecurityTypes VncAuth -passwd /home/shaojiemike/.vnc/passwd :2 to connect to the VNC server.

客户端转发

1
ssh -L 5901:127.0.0.1:5901 -N -f -l shaojiemike node5.xydustc.me

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献