BNU-FZH

fengzhenhua@outlook.com

在许多编程语言和日常工作中都会遇到斜杠和反斜杠。它们的具体解释为:

  • 斜杠(slash): “ / ”是斜杠,因为它的顶端是向前面倾斜的,英文中有时候也叫做“forward slash”;

  • 反斜杠(backslash):“\”是反斜杠,注意,它的顶端是向后倾斜的。要将它和普通的斜杠(“/”)区分开来。

斜杠经常会用来在诸如Unix的文件系统中和万维网网址中表示目录和子目录路径等。很遗憾的是,很多人都把反斜杠(“\”)当做是普通斜杠(“/”)的某种技术用语,错误的使用术语,混淆斜杠和反斜杠的风险让那些足够了解他们的人可以区分他们,但是了解仍不够他们意识到万维网的地址中很少会包含反斜杠(“\”)的。

Windows 和 Linux 的路径区别

  • 路径分隔符不同:Windows使用反斜杠“\”,而Linux使用正斜杠“/”。
  • 文件名大小写敏感:Windows不区分大小写,Linux区分大小写。
  • 文件系统根目录不同:Windows的根目录是盘符(如C:\),Linux的根目录是“/”。
  • 文件权限不同:Linux采用基于权限的访问控制,Windows采用基于用户的访问控制。
  • 文件扩展名不同:Windows的文件扩展名以“.”作为分隔符,Linux通常没有特定的分隔符

Linux Shell 参数续行

Shell命令行下反斜线"\"有两种含义:

对有特殊含义的字符进行转义

例如 echo "123\ \$",结果是"123$"

\Enter, 反斜杠后面紧跟回车,表示下一行是当前行的续航

1
2
3
4
5
6
7
./configure --sbin-path=/usr/local/nginx/nginx \
--conf-path=/usr/local/nginx/nginx.conf \
--pid-path=/usr/local/nginx/nginx.pid \
--with-http\_ssl\_module \
--with-pcre=/usr/local/src/pcre-8.21 \
--with-zlib=/usr/local/src/zlib-1.2.8 \
--with-openssl=/usr/local/src/openssl-1.0.1c

Python 多行语句

Python 通常是一行写完一条语句,但如果语句很长,我们可以使用反斜杠 \\ 来实现多行语句,例如:

1
2
3
total = item_one + \
item_two + \
item_three

[], {}, 或 () 中的多行语句,不需要使用反斜杠 \\,例如:

1
2
total = ['item_one', 'item_two', 'item_three',
'item_four', 'item_five']

LaTeX 中的反斜杠

LaTeX 中的命令通常是由一个反斜杠加上命令名称,再加上花括号内的参数构成的。

1
\documentclass{ctexart}

LaTeX 中有许多字符都有特殊的意义, LaTeX 中的保留字符有 \#,\$,\%,\&,\_,{ }, \,这些在正文中都不能直接呈现,需借助反斜杠转义。

为了实现远程文件访问,决定在ArchLinux上部署SFTP, 虽然技术上仍然没有测试成功,但是先记录下参考的主要文档How to set up an SFTP server on Arch Linux,以期日后解决。

In this guide we are going to set up an sftp server on an Arch Linux system. We will also set up a form of chroot where users can only access sftp with the shared credentials.

The File Transfer Protocol is a standard communication protocol used for the transfer of computer files from a server to a client on a computer network.

FTP isn’t popular today because it Lacks Security. When a file is sent using this protocol, the data, username, and password are all shared in plain text, which means a hacker can access this information with little to no effort. For data to be secure, you need to use an upgraded version of FTP like SFTP.

SFTP Secure File Transfer Protocol is a file transfer protocol that provide secure access to a remote computer to deliver secure communications. It leverages SSH – Secure Socket Shell and is frequently also referred to as ‘Secure Shell File Transfer Protocol’.

Prerequisites

To follow along this guide ensure you have the following:

  1. Arch Linux machine
  2. Root access to the server or a user with root access
  3. Internet access from the server

Table of Content

  1. Ensuring that the server is up to date
  2. Ensuring that the SSH service is installed
  3. Creating users and groups and adding the necessary directories
  4. Configuring the ssh service
  5. Verifying that the set up is working as expected

1. Ensuring that the server is up to date

Before proceeding, ensure your system is up to date. Use this command to refresh the system packages and update them.

1
sudo pacman -Syyu

2. Ensuring that the SSH service is installed

Verify that the ssh is installed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ sudo pacman -Qi openssh
Name : openssh
Version : 8.6p1-1
Description : Premier connectivity tool for remote login with the SSH protocol
Architecture : x86_64
URL : https://www.openssh.com/portable.html
Licenses : custom:BSD
Groups : None
Provides : None
Depends On : glibc krb5 openssl libedit ldns libxcrypt libcrypt.so=2-64 zlib pam
Optional Deps : xorg-xauth: X11 forwarding
x11-ssh-askpass: input passphrase in X
libfido2: FIDO/U2F support
Required By : None
Optional For : None
Conflicts With : None
Replaces : None
Installed Size : 5.79 MiB
Packager : Giancarlo Razzolini <grazzolini@archlinux.org>
Build Date : Mon 19 Apr 2021 11:32:46 AM UTC
Install Date : Thu 03 Jun 2021 03:23:32 AM UTC
Install Reason : Explicitly installed
Install Script : Yes
Validated By : Signature

If ssh is not installed, install with this command:

1
sudo pacman -S openssh

Now that it is installed, start the service

1
sudo systemctl start sshd

Confirm its status

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ sudo systemctl status sshd
● sshd.service - OpenSSH Daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: disabled)
Active: active (running) since Fri 2021-12-03 10:19:02 UTC; 17min ago
Main PID: 467 (sshd)
Tasks: 1 (limit: 4606)
Memory: 5.0M
CGroup: /system.slice/sshd.service
└─467 sshd: /usr/bin/sshd -D [listener] 0 of 10-100 startups

Dec 04 14:53:30 ip-10-2-40-103 sshd[13109]: Unable to negotiate with 141.98.10.246 port 34078: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group-exc>
Dec 04 14:53:42 ip-10-2-40-103 sshd[13111]: Unable to negotiate with 141.98.10.246 port 38674: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group-exc>
Dec 04 14:53:53 ip-10-2-40-103 sshd[13115]: Unable to negotiate with 141.98.10.246 port 43268: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group-exc>
Dec 04 14:54:05 ip-10-2-40-103 sshd[13117]: Unable to negotiate with 141.98.10.246 port 47864: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group-exc>
Dec 04 14:54:17 ip-10-2-40-103 sshd[13119]: Unable to negotiate with 141.98.10.246 port 52460: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group-exc>
Dec 04 14:54:41 ip-10-2-40-103 sshd[13123]: Unable to negotiate with 141.98.10.246 port 33418: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group-exc>
Dec 04 14:54:53 ip-10-2-40-103 sshd[13127]: Unable to negotiate with 141.98.10.246 port 38014: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group-exc>
Dec 04 14:55:05 ip-10-2-40-103 sshd[13129]: Unable to negotiate with 141.98.10.246 port 42614: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group-exc>
Dec 04 15:16:10 ip-10-2-40-103 sshd[13191]: Received disconnect from 61.177.173.21 port 60983:11: [preauth]
Dec 04 15:16:10 ip-10-2-40-103 sshd[13191]: Disconnected from authenticating user root 61.177.173.21 port 60983 [preauth]

3. Creating users and groups and adding the necessary directories

Next we will ensure that the necessary users are present in the system. In my case, I would like to have the sftp users home as /srv/sftp

Let us create the home /srv/sftp with this command:

1
sudo mkdir /srv/sftp

Then let us create an umbrella group for SFTP only

1
sudo groupadd sftpusers

Then create an sftp only user called citizix:

1
sudo useradd -G sftpusers -d /srv/sftp/citizix -s /sbin/nologin citizix

The above options do the following:

  • -G sftpusers: Create user, append to sftpusers group
  • -d /srv/sftp/citizix: Set home dir as /srv/sftp/citizix
  • -s /sbin/nologin: We do not want the user to login, so no ssh login shell
  • Finally, username as citizix

Then add password to the created user using this command:

1
2
3
4
5
$ sudo passwd citizix
Changing password for user citizix.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.

3. Configuring the ssh service

Now that we have installed the necessary software and created the users and groups, let us configure ssh.

Ensure password authentication is enabled for ssh. Edit the config file here /etc/ssh/sshd_config:

1
sudo vim /etc/ssh/sshd_config

Then ensure this line is not commented:

1
PasswordAuthentication yes

Next, we need to add rules for the users in the sftpusers group to be considered as sftp. Edit the config file:

1
sudo vim /etc/ssh/sshd_config

Add this content at the bottom of the file:

1
2
3
4
5
Match Group sftpusers  
X11Forwarding no
AllowTcpForwarding no
ChrootDirectory /srv/sftp
ForceCommand internal-sftp

Then restart sshd to reload the config:

1
sudo systemctl restart sshd

Verify that sshd is running as expected:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ sudo systemctl status sshd
● sshd.service - OpenSSH Daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: disabled)
Active: active (running) since Sat 2021-12-04 15:48:19 UTC; 18s ago
Main PID: 14269 (sshd)
Tasks: 1 (limit: 4606)
Memory: 892.0K
CGroup: /system.slice/sshd.service
└─14269 sshd: /usr/bin/sshd -D [listener] 0 of 10-100 startups

Dec 04 15:48:19 ip-10-2-40-103 systemd[1]: Started OpenSSH Daemon.
Dec 04 15:48:19 ip-10-2-40-103 sshd[14269]: Server listening on 0.0.0.0 port 22.
Dec 04 15:48:19 ip-10-2-40-103 sshd[14269]: Server listening on :: port 22.

4. Verifying that the set up is working as expected

After successfully creating the user and adding sftp configurations, let is test the set up using the command:

1
2
3
4
❯ sftp citizix@10.2.11.8
citizix@<meta charset="utf-8">10.2.11.8's password:
Connected to <meta charset="utf-8">10.2.11.8.
sftp>

Now we have sftp server up and running with a user configured!

The users will be able to login to the server and access files and directories located in their home directory. If you want to give the user to other directories outside their own directory, just make sure the user has enough rights to access. These directories and files have to be within the sftp directory – /srv/sftp.

Example: if i want user to access the directory /srv/sftp/paymentfiles, do the following:

Create the directory

1
sudo mkdir /srv/sftp/paymentfiles

Then assign the user(citizix) access by making them own the directory:

1
sudo chown citizix:sftpusers /srv/sftp/paymentfiles

That is it. Users should now have access.

Conclusion

We managed to set up sftp server in an Archlinux Server in this guide.

为什么需要局域网

一般工作过程中会有一个台式机电脑和一个笔记本电脑,为了提高效率两台电脑均安装了固态硬盘. 但是我们还会遇到了文件传输的问题, 比如在台式机上的文件需要传输到笔记本电脑上,我们可以借助U盘移动硬盘网盘(百度网盘、阿里网盘等), 它们各有优点和不足:

  • U盘在转移资料时最方便,但是仅限于对于小文件,如果文件体积过大,那从台式机传输到U盘需要大量的时间,同时从U盘再到笔记本又需要再消耗几乎相同的时间,而且U盘的传输速度一般比较慢,但是它的最大的优点是一旦资料存储在U盘上,那可以方便的携带到其他电脑,实现多次传输。
  • 移动硬盘容量比较大,性能要远优于U盘, 一些重要的资料可以保存的上面,但是对于那些需要经常读取和编辑的资料就不适合存放在上面,因为移动硬盘经常随身携带也远不如U盘小巧。但是其传输速度非常快,特别是使用固态硬盘组装的移动硬盘,但是移动硬盘的最大作用应当是备份,备份资料就要求存储介质足够稳定,而目前就价格和稳定性上讲,机械硬盘仍是不二选择,相同价格机械硬盘的容量要远大于固态硬盘,机械硬盘的缺点是在工作时不能随便移动。
  • 网盘兴起有好多年了,目前比较有名的有:百度网盘、阿里云盘、坚果云盘、123网盘和中国移动网盘等. 平时可以把资料存在网盘上,但是你不能保证上传和下载速度,同时还有各种广告,各种限制,以及有些涉及隐私的资料都不适合存储在网盘,所以就当前来看这是一个不太明智的作法。

综上,在工作中对于那些需要经常访问的资料应当保存的电脑上,同时为了方便在不同电脑中传输,本文提供一个新的方法:使用网线组成局域网,借助samba在局域网中分享文件,分享方法参考:ArchLinux 局域网共享文件.

使用移动硬盘等中间媒介时,其传输速度要高于局域网。在测试时,我发现使用局域网传输时速度稳定在60M左右,但是局域网的总速度是1000M,这就意味着可以同时传输多个文件夹,每个传输速度都会在60M左右,显然多线程可以弥补移动硬盘单一高速的优势,但是局域网的点对点的直达优势却是移动硬盘所不具备的. 如果两台电脑处于同一个局域网,或两台电脑就在身边且有一根网线,则推荐使用局域网. 如果两电脑距离较远,且所需资料是不经常编辑的,则推荐使用移动硬盘。

设置局域网的方法

1. 准备工作

在开始之前,请准备好:

  • 两台运行Ubuntu操作系统的电脑。
  • 一根网线(建议不要超过80米,否则信号会衰减)。

2. 连接网线

将网线一端插入第一台电脑的网口,将另一端插入另一电脑的网口。确保网线插紧并且连接稳固。

3. 配置网络

  • 打开第一台Ubuntu电脑的系统设置(Settings)。
  • 选择“网络”(Network)选项。
  • 在网络设置中,点击“有线连接”(Wired Connection)。
  • 确保“有线连接”开关处于打开状态。
  • 选择需要修改的以太网设置,点击右侧的“选项”(Options)按钮(齿轮形的按钮),进入高级网络设置。
  • 在IPv4设置中,选择“手动”(Manual)。
  • 点击“添加”按钮,添加一个新的IP地址。
  • 在“地址”(Address)字段中输入IP地址,例如:192.168.189.13。子网掩码通常为255.255.255.0
  • 在“网关”(Gateway)字段中输入网关地址,例如:192.168.189.1。设置完毕的窗口如下图所示:
    置局域网网关
  • 点击“应用”并关闭网络设置窗口。

解释:

(1)为什么要设置成192.168.189.13?其他可以吗?
IP地址是局域网中用来标识设备的地址,而192.168.189.13是IPv4地址中的一个私有地址段,通常用于局域网中。在一个子网内,每台设备的IP地址应该是唯一的,因此我们选择一个未被占用的地址,以便在同一网络中找到对应的设备。其他私有地址段,如192.168.0.0192.168.1.0等也可以使用,只要保证两台电脑在同一个子网内,并且IP地址唯一。

(2)网关怎么设置?
本教程选择了192.168.189.1这个地址作为网关,是因为它处于私有IP地址范围内(私有IP地址范围是指专门供局域网使用的IP地址范围,不会在全球互联网上被路由器转发),并且与我们选择的IP地址192.168.189.x在同一个子网中。只需要保证两台电脑上的网关相同即可。

(3)子网掩码为什么是255.255.255.0
子网掩码用于定义哪些部分是网络地址,哪些部分是主机地址。在一个局域网中,通常使用的子网掩码是255.255.255.0,这意味着前三个字节是网络地址,最后一个字节是主机地址。这样,同一子网内的设备可以相互通信,而不同子网的设备则需要通过路由器进行通信。

4. 配置第二台电脑

重复步骤三,但在IPv4设置中将IP地址更改为另一个可用的地址,例如:192.168.189.8。子网掩码为255.255.255.0,网关字段中输入相同的网关地址:192.168.189.1
第二台电脑网关设置

5. 测试连接

打开终端,使用ping命令测试两台电脑之间的连接。在第一台电脑上输入以下命令:

1
ping 192.168.189.8

如果一切正常,你应该会收到类似以下的回复:

1
64 bytes from 192.168.189.8: icmp_seq=1 ttl=64 time=0.504 ms

在第二台电脑上输入以下命令:

1
ping 192.168.189.13

如果一切正常,你应该会收到类似以下的回复:

1
64 bytes from 192.168.189.13: icmp_seq=1 ttl=64 time=0.425 ms

这意味着两台电脑之间的网络连接已经建立并且正常工作。

macOS和Windows用户,可以按照类似的步骤进行网线连接和网络配置。在macOS上,你可以打开“系统偏好设置”,选择“网络”,然后配置IP地址、子网掩码和网关等参数。而在Windows操作系统上,可以通过“控制面板”中的“网络和共享中心”来进行相似的设置。(如果无法A可以ping通B,B无法ping通A,请检查A的防火墙设置)

安装必备软件

1
sudo pacman -S nautilus-share

注意:安装nautilus-share插件后,pacman将会自动安装上samba等依赖软件,所以仅此一条指令就可以实现gnome3下的nautilus配置共享目录。

启用用户共享功能

在用户可以共享目录之前,管理员必须在 Samba 中启用用户共享。本节参考:3.11. 允许用户在 Samba 服务器上共享目录

例如,仅允许本地 example 组的成员创建用户共享:

  1. 如果example不存在,则创建它

    1
    sudo groupadd example

  2. Samba准备目录以存储用户共享定义并正确设置其权限。例如:

    • 创建目录

      1
      sudo mkdir -p /var/lib/samba/usershares/

    • example组设置权限

      1
      2
      sudo chgrp example /var/lib/samba/usershares/
      sudo chmod 1770 /var/lib/samba/usershares/

    • 设置粘性位以防止用户重命名或删除此目录中其他用户存储的文件。

  3. /etc/samba/smb.conf文件不存在,则参考smb.conf.default建立smb.conf,并将以下内容添加到[global]部分(默认配置中并没有这两项):

    • 设置您配置用来存储用户共享定义的目录的路径。例如:

      1
      usershare path = /var/lib/samba/usershares/

    • 设置允许在这个服务器上创建多少个用户共享 Samba。例如:

      1
      usershare max shares = 100

    如果您对 usershare max shares 参数使用默认值 0,则用户共享将被禁用。

  4. 验证/etc/samba/smb.conf文件

    1
    testparm

  5. 重新载入Samba配置

    1
    sudo smbcontrol all reload-config

用户现在可以创建用户共享。

  1. 支持软链接访问(可选)

/etc/samba/smb.conf[global]节的最后,加上下面三条设置:

1
2
3
follow symlinks = yes
wide links = yes
unix extensions = no

  1. 重启samba服务
    1
    sudo systemctl restart smb

参考文章: Ubuntu配置samba服务器

ArchLinux 中共享文件夹的步骤

步骤 1: 打开文件管理器,右键单击共享的文件夹。点击上下文菜单中的“本地网络共享”选项。

本地网络共享选项

步骤 2: 在文件夹共享对话框中点击共享文件夹复选框。

这将在你的系统中安装 Samba 软件包。Samba 用于在 Windows 和 Unix 系统之间通过网络共享文件和打印机。

文件夹共享选项 - 安装 Samba

步骤 3: 安装 Samba 后,执行以下操作共享文件夹或目录。

  • 选中共享文件夹复选框。
  • 输入共享名称。这将是你从另一个系统(如 Windows)看到的名称。尽量不要使用任何带有空格的名称。
  • (可选)通过勾选相应选项,你可以控制共享文件夹的写入权限,以及允许访客访问。
  • 如果你允许访客访问,则没有凭据的人可以访问共享文件夹。所以要谨慎。
  • 如果你希望用户输入用户名和密码,打开终端并运行以下命令。
1
sudo smbpasswd -a 用户名

用户名 应该是对应 ArchLinux 系统的有效用户。

现在,你应该已经设置好了共享的文件夹或目录。

如何访问共享文件夹

从 Linux 系统中访问共享文件夹,你需要系统的 IP 地址或主机名。为此,打开“系统设置System Settings -> Wi-Fi -> 获取 IP 地址Get the IP address”。

IP 地址设置

如果你运行的是 Linux 发行版不是 Ubuntu,此步骤略有不同。你可能想运行 ip addr 来获取 IP 地址,如下所示:

在 Linux 中查找 IP 地址

一旦你获得 IP 地址,就可以在 Linux 系统中打开文件管理器,然后在地址栏中输入以下内容。注意:你应该修改为你系统的 IP 地址

你现在可以看到共享文件夹上面显示了一个小共享图标,表示网络共享文件夹。

共享文件夹

要在 Windows 系统 访问共享文件夹,打开运行(按下 Windows + R)或打开资源管理器,输入以下地址。注意:你应该修改为你系统的 IP 地址和文件夹名称。

1
\\192.168.43.19\Folder

你应该能够查看共享文件夹的内容,并根据授予的权限修改它。本节参考:如何在 Ubuntu/Linux 和 Windows 之间共享文件夹

关闭 samba 异步提高网速

默认情况下,Samba写操作是异步的,这导致了读取速度减半。在千兆局域网速下,Samba写入速度应当在100m/s, 但是读取速度只有50m/s, 解决方法为:关闭异步, 在配置文件/etc/samba/smb.conf[global]节点下追加上如下代码

/etc/samba/smb.conf -- [global]
1
aio read size = 0

在购买内存时, 如果你的主板只有两个内存插槽,你应该选择购买一条 16GB 的内存还是两条 8GB 的内存呢?当预算有限时,我们需要做出抉择。

内存容量大有什么优势

简单来说,不同内存配置的差异在于,一条16GB内存的价格较便宜,并且占用的内存插槽较少。而两条8G内存的优势在于可以启用双通道技术,从而在一定程度上提升性能。计算机在工作时,CPU直接与内存交换数据,内存容量越大,能够存放更多的文件进行处理,并提供更大的空间用于与CPU交换数据,从而更轻松地处理大型软件和多任务。

双通道内存有什么优势

内存的读写速度也会影响电脑的运行速度。双通道内存具备更大的带宽,可以实现更快的内存读取速度。虽然,但在使用时,可以。特别强调的是,,许多游戏的帧数可以提升50%甚至更多。而对于来说,双通道内存的帧数提升则,因此

先保证大内存还是先保证双通道

了解了前述知识点后,我们可以得出结论:

  • 如果你使用的是核显,并且没有独立显卡,一定要选择两条8GB内存以实现双通道,尤其是对于有游戏和图像处理需求的用户来说,这一点要优先考虑。
  • 如果你使用的是独立显卡,并且主板具备四个内存插槽,同样推荐选择两条8GB内存,因为这样还能保留两个内存插槽供未来升级使用。
  • 如果你的主板只有两个内存插槽,并且你担心将来的内存升级,那么在使用独立显卡的情况下,选择一条16GB内存也是可以的,因为

此外,还要注意内存的兼容性,一些较大尺寸的散热器安装后可能会挤占内存插槽的空间,如果主板因此减少了两个内存插槽,那就只能考虑更换硬件了。总之,根据自己的需求和经济情况,选择合适的内存配置是非常重要的。无论是单条16GB还是双条8GB,都有各自的优势和适用场景,希望上述的解释能对DIY玩家们在购买内存时有所帮助。

Linux目录与Windows目录类比

  • /usr:系统级的目录,可以理解为C:/Windows//usr/lib理解为C:/Windows/System32
  • /usr/local:用户级的程序目录,可以理解为C:/Progrem Files/。用户自己编译的软件默认会安装到这个目录下。
  • /opt:用户级的程序目录,可以理解为D:/Softwareopt有可选的意思,这里可以用于放置第三方大型软件(或游戏),当你不需要时,直接rm -rf掉即可。在硬盘容量不够时,也可将/opt单独挂载到其他磁盘上使用。

源码的安装位置

  • /usr/src:系统级的源码目录。
  • /usr/local/src:用户级的源码目录。

/opt

这里主要存放那些可选的程序。例如测试最新的firefox, 那就装到/opt目录,当你测试完,直接删除它即可,这不影响系统其他任何设置。安装到/opt目录下的程序,它所有的数据、库文件等等都是放在同个目录下面。 举个例子:刚才装的测试版firefox,就可以装到/opt/firefox_beta目录下,/opt/firefox_beta目录下面就包含了运 行firefox所需要的所有文件、库、数据等等。要删除firefox的时候,你只需删除/opt/firefox_beta目录即可,非常简单。

/usr/local

这里主要存放那些的软件,即不是通过“包管理器”安装的软件。它和/usr目录具有相类似的目录结构。让软件包管理器来管理/usr目录,而把自定义的脚本(scripts)放到/usr/local目录下面是个不错的主意。

exFAT很好,MAC、WIN都支持,还可以支持大硬盘和大文件 ,但它有一个问题,有时会很严重。如果你拿它存电影、音乐等,或者暴雪的游戏,都没关系,都是大文件。如果你拿它存一堆小文件,每个不超过128K,问题就出现了。

1T硬盘,一个分区:

  • NTFS每个簇4K,就是说你的文件要是10K,占三个簇,浪费2K。
  • exFAT每个簇256K,10K的文件占一个簇,浪费246K。

曾经在装的一个cygwin,4.8G:

  • 在NTFS分区,占5.2G,还好。
  • 在exFAT分区,大概占50G吧,因为多数文件都只有几十K甚至不到1K。

在NTFS里只占不到400G的数据(各种文件,电影、游戏、文档、程序源文件等),移到exFAT都快600G了。

exFAT分区小于512G的话,每个簇128K,还稍微节约一点,所以可以分至少两个区,每个512G。

一个NTFS,存放一堆小文件,或者变化不大的文件,可以用虚拟机里的WIN系统来修改。另一个是exFAT,放大文件或经常修改的文件。还有一个方案就是把一堆小文件弄成磁盘映像,DMG或ISO,只要装载了就可以直接用,但里面的内容不方便修改。存成压缩文件亦可,都不会显出exFAT的问题。

在格式化U盘的时候,会有一个U盘格式的选择,一般是两个,有的是三个,FAT32/exFAT/NTFS,那么这三个格式具体有什么区别呢?相信很多人都知道如果要拷贝超过4G大小的文件就得用NTFS格式,但其实他们之间的区别可不只这个。

首先先给大家介绍下这三种格式的特点。

FAT32

FAT32文件系统用4个字节(32位)空间来表示每个扇区配置文件的情形,所以叫FAT32。分区容量最低是512M,而上限的话不同的操作系统都不一样,WinXP系统最大可以做到2TB的FAT32分区。

NTFS

NTFS文件系统是windows NT核心和高级服务器网络操作系统环境的文件系统。NTFS系统比FAT32的可靠性更高,可以支持更大的分区和更大的文件,此外还有不少FAT32没有的功能,比如压缩分区、文件索引、数据保护和恢复、加密访问等。

exFAT

exFAT文件系统是微软在windows embeded5.0以上引入的一种适合于闪存的文件系统,主要是为了解决FAT32不支持4G或更大文件的问题而推出的。

FAT32与exFAT可用4GB文件的区别:

大多数优盘在格式化时默认FAT32,最大优点就是在一个不超过8GB的分区中,FAT32的每个簇容量都固定为4KB,与前代相比可以大大地减少磁盘的浪费,提高磁盘利用率。

虽然对于最大分区容量的支持上面,FAT32的2TB最大分区容量至今仍不过时,但FAT32无法传输并存放超过4GB容量的光盘ISO镜像、高清视频、各种图形作品文件等等,这是最致命的弊端。而exFAT格式在苹果本或者是Windows电脑上都可以格式化,并且在两个系统之间可以互相无障碍使用。相比之下,exFAT格式就没有4GB文件传输限制了。

其实FAT32与exFAT存在着一个升级关系,这两种文件系统都支持OS X系统与Windows系统,如果你将U盘格式化成这两种文件系统,在不同操作系统电脑上可以畅通无阻地使用。

NTFS相比FAT会让闪存性能降低

NTFS是相当流行的,但多见于硬盘。XP、Vista、Win7默认都会将分区格式化为NTFS系统,可以说NTFS是目前最好的磁盘文件系统。

优盘在格式化为NTFS时,实际可用容量与其他文件系统形式也有所差别,虽然使用中感受并不是很大。另外,在格式化U盘时,容量在64G甚至更大规格时,系统识别自动格式化为exFAT与NTFS格式,会便于优盘在不同操作系统电脑上无障碍使用。

但是,NTFS也有他的缺点,NTFS分区采用“日志式”,因为要记录磁盘的详细读写操作,对U盘这种快闪存储介质会造成较大的负担,比如同样存取一个文件或目录,在NTFS上的读写次数就会比FAT32更多,理论上NTFS格式的U盘比较容易损坏。并且由于U盘带宽有限,NTFS频繁读写占据通道会让磁盘性能降低。

以上便是关于U盘文件系统FAT32、exFAT、NTFS之间的区别和特点介绍,FAT32与exFAT之间最大的差别在于能否支持4GB及以上容量单个文件的传输、查看与编辑;而FAT系列文件系统与NTFS之间最大的区分在于文件存取的方式,后者长期使用可能会让闪存类存储产品性能降低.

本文主要介绍CUDA编程的基本流程和核心概念,并使用Python Numba编写GPU并行程序。

  1. GPU硬件知识和基础概念:包括CPU与GPU的区别、GPU架构、CUDA软件栈简介。

  2. GPU编程入门:主要介绍CUDA核函数,Thread、Block和Grid概念,并使用Python Numba进行简单的并行计算。

  3. GPU编程进阶:主要介绍一些优化方法。

  4. GPU编程实践:使用Python Numba解决复杂问题。

针对Python的CUDA教程

Python是当前最流行的编程语言,被广泛应用在深度学习、金融建模、科学和工程计算上。作为一门解释型语言,它运行速度慢也常常被用户诟病。著名Python发行商Anaconda公司开发的Numba库为程序员提供了Python版CPU和GPU编程工具,速度比原生Python快数十倍甚至更多。使用Numba进行GPU编程,你可以享受:

  1. Python简单易用的语法;
  2. 极快的开发速度;
  3. 成倍的硬件加速。

为了既保证Python语言的易用性和开发速度,又达到并行加速的目的,本系列主要从Python的角度给大家分享GPU编程方法。关于Numba的入门可以参考我的另一篇文章。更加令人兴奋的是,Numba提供了一个GPU模拟器,即使你手头暂时没有GPU机器,也可以先使用这个模拟器来学习GPU编程!

初识GPU编程

兵马未动,粮草先行。在开始GPU编程前,需要明确一些概念,并准备好相关工具。

CUDA是英伟达提供给开发者的一个GPU编程框架,程序员可以使用这个框架轻松地编写并行程序。本系列第一篇文章提到,CPU和主存被称为主机(Host),GPU和显存(显卡内存)被称为设备(Device),CPU无法直接读取显存数据,GPU无法直接读取主存数据,主机与设备必须通过总线(Bus)相互通信。

GPU和CPU架构

在进行GPU编程前,需要先确认是否安装了CUDA工具箱,可以使用echo $CUDA_HOME检查CUDA环境变量,返回值不为空说明已经安装好CUDA。也可以直接用Anaconda里的conda命令安装CUDA:

1
$ conda install cudatoolkit

然后可以使用nvidia-smi命令查看显卡情况,比如这台机器上几张显卡,CUDA版本,显卡上运行的进程等。我这里是一台32GB显存版的Telsa V100机器。

nvidia-smi命令返回结果

安装Numba库:

1
$ conda install numba

检查一下CUDA和Numba是否安装成功:

1
2
from numba import cuda
print(cuda.gpus)

如果上述步骤没有问题,可以得到结果:<Managed Device 0>...。如果机器上没有GPU或没安装好上述包,会有报错。CUDA程序执行时会独霸一张卡,如果你的机器上有多张GPU卡,CUDA默认会选用0号卡。如果你与其他人共用这台机器,最好协商好谁在用哪张卡。一般使用CUDA_VISIBLE_DEVICES这个环境变量来选择某张卡。如选择5号GPU卡运行你的程序。

1
CUDA_VISIBLE_DEVICES='5' python example.py

如果手头暂时没有GPU设备,Numba提供了一个模拟器,供用户学习和调试,只需要在命令行里添加一个环境变量。

Mac/Linux:

1
export NUMBA_ENABLE_CUDASIM=1

Windows:

1
SET NUMBA_ENABLE_CUDASIM=1

需要注意的是,模拟器只是一个调试的工具,在模拟器中使用Numba并不能加速程序,有可能速度更慢,而且在模拟器能够运行的程序,并不能保证一定能在真正的GPU上运行,最终还是要以GPU为准。

有了以上的准备工作,我们就可以开始我们的GPU编程之旅了!

GPU程序与CPU程序的区别

一个传统的CPU程序的执行顺序如下图所示:

CPU程序执行流程

CPU程序是顺序执行的,一般需要:

  1. 初始化。

  2. CPU计算。

  3. 得到计算结果。

在CUDA编程中,CPU和主存被称为主机(Host),GPU被称为设备(Device)。

GPU程序执行流程

当引入GPU后,计算流程变为:

  1. 初始化,并将必要的数据拷贝到GPU设备的显存上。
  2. CPU调用GPU函数,启动GPU多个核心同时进行计算。
  3. CPU与GPU异步计算。
  4. 将GPU计算结果拷贝回主机端,得到计算结果。

一个名为gpu_print.py的GPU程序如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from numba import cuda

def cpu_print():
print("print by cpu.")

@cuda.jit
def gpu_print():
# GPU核函数
print("print by gpu.")

def main():
gpu_print[1, 2]()
cuda.synchronize()
cpu_print()

if __name__ == "__main__":
main()

使用CUDA_VISIBLE_DEVICES='0' python gpu_print.py执行这段代码,得到的结果为:

1
2
3
print by gpu.
print by gpu.
print by cpu.

与传统的Python CPU代码不同的是:

  • 使用from numba import cuda引入cuda
  • 在GPU函数上添加@cuda.jit装饰符,表示该函数是一个在GPU设备上运行的函数,GPU函数又被称为核函数
  • 主函数调用GPU核函数时,需要添加如[1, 2]这样的执行配置,这个配置是在告知GPU以多大的并行粒度同时进行计算。gpu_print[1, 2]()表示同时开启2个线程并行地执行gpu_print函数,函数将被并行地执行2次。下文会深入探讨如何设置执行配置。
  • GPU核函数的启动方式是异步的:启动GPU函数后,CPU不会等待GPU函数执行完毕才执行下一行代码。必要时,需要调用cuda.synchronize(),告知CPU等待GPU执行完核函数后,再进行CPU端后续计算。这个过程被称为同步,也就是GPU执行流程图中的红线部分。如果不调用cuda.synchronize()函数,执行结果也将改变,"print by cpu.将先被打印。虽然GPU函数在前,但是程序并没有等待GPU函数执行完,而是继续执行后面的cpu_print函数,由于CPU调用GPU有一定的延迟,反而后面的cpu_print先被执行,因此cpu_print的结果先被打印了出来。

Thread层次结构

前面的程序中,核函数被GPU并行地执行了2次。在进行GPU并行编程时需要定义执行配置来告知以怎样的方式去并行计算,比如上面打印的例子中,是并行地执行2次,还是8次,还是并行地执行20万次,或者2000万次。2000万的数字��大,远远多于GPU的核心数,如何将2000万次计算合理分配到所有GPU核心上。解决这些问题就需要弄明白CUDA的Thread层次结构。

并行执行8次的执行配置

CUDA将核函数所定义的运算称为线程(Thread),多个线程组成一个块(Block),多个块组成网格(Grid)。这样一个grid可以定义成千上万个线程,也就解决了并行执行上万次操作的问题。例如,把前面的程序改为并行执行8次:可以用2个block,每个block中有4个thread。原来的代码可以改为gpu_print[2, 4](),其中方括号中第一个数字表示整个grid有多少个block,方括号中第二个数字表示一个block有多少个thread。

实际上,线程(thread)是一个编程上的软件概念。从硬件来看,thread运行在一个CUDA核心上,多个thread组成的block运行在Streaming Multiprocessor(SM的概念详见本系列第一篇文章),多个block组成的grid运行在一个GPU显卡上。

软硬件对应关系

CUDA提供了一系列内置变量,以记录thread和block的大小及索引下标。以[2, 4]这样的配置为例:blockDim.x变量表示block的大小是4,即每个block有4个thread,threadIdx.x变量是一个从0到blockDim.x - 1(4-1=3)的索引下标,记录这是第几个thread;gridDim.x变量表示grid的大小是2,即每个grid有2个block,blockIdx.x变量是一个从0到gridDim.x - 1(2-1=1)的索引下标,记录这是第几个block。

CUDA内置变量示意图

某个thread在整个grid中的位置编号为:threadIdx.x + blockIdx.x * blockDim.x

使用内置变量计算某个thread编号

利用上述变量,我们可以把之前的代码丰富一下:

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
``from numba import cuda

def cpu_print(N):
for i in range(0, N):
print(i)

@cuda.jit
def gpu_print(N):
idx = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x
if (idx < N):
print(idx)

def main():
print("gpu print:")
gpu_print[2, 4](8)
cuda.synchronize()
print("cpu print:")
cpu_print(8)

if __name__ == "__main__":
main()`

执行结果为:

```py
``gpu print:
0
1
2
3
4
5
6
7
cpu print:
0
1
2
3
4
5
6
7`

这里的GPU函数在每个CUDA thread中打印了当前thread的编号,起到了CPU函数`for`循环同样的作用。因为`for`循环中的计算内容互相不依赖,也就是说,某次循环只是专心做自己的事情,循环第i次不影响循环第j次的计算,所以这样互相不依赖的`for`循环非常适合放到CUDA thread里做并行计算。在实际使用中,我们一般将CPU代码中互相不依赖的的`for`循环适当替换成CUDA代码。

这份代码打印了8个数字,核函数有一个参数`N`,`N = 8`,假如我们只想打印5个数字呢?当前的执行配置共2 \* 4 = 8个线程,线程数8与要执行的次数5不匹配,不过我们已经在代码里写好了`if (idx < N)`的判断语句,判断会帮我们过滤不需要的计算。我们只需要把`N = 5`传递给`gpu_print`函数中就好,CUDA仍然会启动8个thread,但是大于等于`N`的thread不进行计算。==注意,当线程数与计算次数不一致时,一定要使用这样的判断语句,以保证某个线程的计算不会影响其他线程的数据。==

![线程数与计算次数不匹配](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/20/16e86a62f739c803~tplv-t2oaga2asx-jj-mark:3024:0:0:0:q75.png)

## Block大小设置

不同的执行配置会影响GPU程序的速度,一般需要多次调试才能找到较好的执行配置,在实际编程中,执行配置`[gridDim, blockDim]`应参考下面的方法:

- block运行在SM上,不同硬件架构(Turing、Volta、Pascal...)的CUDA核心数不同,一般需要根据当前硬件来设置block的大小`blockDim`(执行配置中第二个参数)。一个block中的thread数最好是32128256的倍数。==注意,限于当前硬件的设计,block大小不能超过1024。==
- grid的大小`gridDim`(执行配置中第一个参数),即一个grid中block的个数可以由总次数`N`除以`blockDim`,并向上取整。

例如,我们想并行启动1000个thread,可以将blockDim设置为128,`1000 ÷ 128 = 7.8`,向上取整为8。使用时,执行配置可以写成`gpuWork[8, 128]()`,CUDA共启动`8 * 128 = 1024`个thread,实际计算时只使用前1000个thread,多余的24个thread不进行计算。

注意,这几个变量比较容易混淆,再次明确一下:`blockDim`是block中thread的个数,一个block中的`threadIdx`最大不超过`blockDim`;`gridDim`是grid中block的个数,一个grid中的`blockIdx`最大不超过`gridDim`。

以上讨论中,block和grid大小均是一维,实际编程使用的执行配置常常更复杂,block和grid的大小可以设置为二维甚至三维,如下图所示。这部分内容将在下篇文章中讨论。

![Thread Block Grid](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/20/16e86a62ef74074b~tplv-t2oaga2asx-jj-mark:3024:0:0:0:q75.png)

## 内存分配

前文提到,GPU计算时直接从显存中读取数据,因此每当计算时要将数据从主存拷贝到显存上,用CUDA的术语来说就是要把数据从主机端拷贝到设备端。CUDA强大之处在于它能自动将数据从主机和设备间相互拷贝,不需要程序员在代码中写明。这种方法对编程者来说非常方便,不必对原有的CPU代码做大量改动。

我们以一个向量加法为例,编写一个向量加法的核函数如下:

```py
py@cuda.jit
def gpu_add(a, b, result, n):
# a, b为输入向量,result为输出向量
# 所有向量都是n维
# 得到当前thread的索引
idx = cuda.threadIdx.x + cuda.blockDim.x * cuda.blockIdx.x
if idx < n:
result[idx] = a[idx] + b[idx]

初始化两个2千万维的向量,作为参数传递给核函数:

1
2
3
4
5
6
7
8
9
10
n = 20000000
x = np.arange(n).astype(np.int32)
y = 2 * x
gpu_result = np.zeros(n)

# CUDA执行配置
threads_per_block = 1024
blocks_per_grid = math.ceil(n / threads_per_block)

gpu_add[blocks_per_grid, threads_per_block](x, y, gpu_result, n)

把上述代码整合起来,与CPU代码做对比,并验证结果正确性:

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
from numba import cuda
import numpy as np
import math
from time import time

@cuda.jit
def gpu_add(a, b, result, n):
idx = cuda.threadIdx.x + cuda.blockDim.x * cuda.blockIdx.x
if idx < n:
result[idx] = a[idx] + b[idx]

def main():
n = 20000000
x = np.arange(n).astype(np.int32)
y = 2 * x

gpu_result = np.zeros(n)
cpu_result = np.zeros(n)

threads_per_block = 1024
blocks_per_grid = math.ceil(n / threads_per_block)
start = time()
gpu_add[blocks_per_grid, threads_per_block](x, y, gpu_result, n)
cuda.synchronize()
print("gpu vector add time " + str(time() - start))
start = time()
cpu_result = np.add(x, y)
print("cpu vector add time " + str(time() - start))

if (np.array_equal(cpu_result, gpu_result)):
print("result correct")

if __name__ == "__main__":
main()

运行结果,GPU代码竟然比CPU代码慢10+倍!

1
2
3
gpu vector add time 13.589356184005737
cpu vector add time 1.2823548316955566
result correct

说好的GPU比CPU快几十倍上百倍呢?这里GPU比CPU慢很多原因主要在于:

  1. 向量加法的这个计算比较简单,CPU的numpy已经优化到了极致,无法突出GPU的优势,我们要解决实际问题往往比这个复杂得多,当解决复杂问题时,优化后的GPU代码将远快于CPU代码。
  2. 这份代码使用CUDA默认的统一内存管理机制,没有对数据的拷贝做优化。CUDA的统一内存系统是当GPU运行到某块数据发现不在设备端时,再去主机端中将数据拷贝过来,当执行完核函数后,又将所有的内存拷贝回主存。在上面的代码中,输入的两个向量是只读的,没必要再拷贝回主存。
  3. 这份代码没有做流水线优化。CUDA并非同时计算2千万个数据,一般分批流水线工作:一边对2000万中的某批数据进行计算,一边将下一批数据从主存拷贝过来。计算占用的是CUDA核心,数据拷贝占用的是总线,所需资源不同,互相不存在竞争关系。这种机制被称为流水线。这部分内容将在下篇文章中讨论。

原因2中本该程序员动脑思考的问题交给了CUDA解决,增加了时间开销,所以CUDA非常方便的统一内存模型缺点是计算速度慢。针对原因2,我们可以继续优化这个程序,告知GPU哪些数据需要拷贝到设备,哪些需要拷贝回主机。

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
from numba import cuda
import numpy as np
import math
from time import time

@cuda.jit
def gpu_add(a, b, result, n):
idx = cuda.threadIdx.x + cuda.blockDim.x * cuda.blockIdx.x
if idx < n :
result[idx] = a[idx] + b[idx]

def main():
n = 20000000
x = np.arange(n).astype(np.int32)
y = 2 * x

# 拷贝数据到设备端
x_device = cuda.to_device(x)
y_device = cuda.to_device(y)
# 在显卡设备上初始化一块用于存放GPU计算结果的空间
gpu_result = cuda.device_array(n)
cpu_result = np.empty(n)

threads_per_block = 1024
blocks_per_grid = math.ceil(n / threads_per_block)
start = time()
gpu_add[blocks_per_grid, threads_per_block](x_device, y_device, gpu_result, n)
cuda.synchronize()
print("gpu vector add time " + str(time() - start))
start = time()
cpu_result = np.add(x, y)
print("cpu vector add time " + str(time() - start))

if (np.array_equal(cpu_result, gpu_result.copy_to_host())):
print("result correct!")

if __name__ == "__main__":
main()

这段代码的运行结果为:

1
2
3
gpu vector add time 0.19940638542175293
cpu vector add time 1.132070541381836
result correct!

至此,可以看到GPU速度终于比CPU快了很多。

Numba对Numpy的比较友好,编程中一定要使用Numpy的数据类型。用到的比较多的内存分配函数有:

  • cuda.device_array(): 在设备上分配一个空向量,类似于numpy.empty()
  • cuda.to_device():将主机的数据拷贝到设备
1
2
ary = np.arange(10)
device_ary = cuda.to_device(ary)
  • cuda.copy_to_host():将设备的数据拷贝回主机
1
host_ary = device_ary.copy_to_host()

总结

Python Numba库可以调用CUDA进行GPU编程,CPU端被称为主机,GPU端被称为设备,运行在GPU上的函数被称为核函数,调用核函数时需要有执行配置,以告知CUDA以多大的并行粒度来计算。使用GPU编程时要合理地将数据在主机和设备间互相拷贝。

GPU程序执行流程

CUDA编程的基本流程为:

  1. 初始化,并将必要的数据拷贝到GPU设备的显存上。
  2. 使用某个执行配置,以一定的并行粒度调用CUDA核函数。
  3. CPU和GPU异步计算。
  4. 将GPU计算结果拷贝回主机。

参考声明

金融建模、自动驾驶、智能机器人、新材料发现、脑神经科学、医学影像分析...人工智能时代的科学研究极度依赖计算力的支持。提供算力的各家硬件芯片厂商中,最抢镜的当属英伟达Nvidia了。这家做显卡起家的芯片公司在深度学习兴起后可红得发紫,如果不聊几句GPU和英伟达,都不好意思跟别人说自己是做人工智能的。那么,英伟达的GPU是如何加速计算呢?本系列将介绍GPU计算加速的一些基础知识:

  1. GPU硬件知识和基础概念:包括CPU与GPU的区别、GPU架构、CUDA软件栈简介。
  2. GPU编程入门:主要介绍CUDA核函数,Thread、Block和Grid概念,并使用Python Numba进行简单的并行计算。
  3. GPU编程进阶:主要介绍多核配置和存储管理。
  4. GPU编程实践:使用Python Numba解决复杂问题。

什么是GPU

GPU全名为Graphics Processing Unit,又称视觉处理器、图形显示卡。GPU负责渲染出2D、3D、VR效果,主要专注于计算机图形图像领域。后来人们发现,GPU非常适合并行计算,可以加速现代科学计算,GPU也因此不再局限于游戏和视频领域。

游戏

CPU和GPU

现代CPU处理数据的速度在纳秒级别,为何还要使用GPU来加速?CPU能被GPU替代吗?

对于计算机体系不了解的朋友可以先阅读我之前的文章,有助于你理解下面的一些概念。

无论是CPU还是GPU,在进行计算时,都需要用核心(Core)来做算术逻辑运算,比如加减乘与或非等。核心中有ALU(逻辑运算单元)和寄存器等电路。在进行计算时,一个核心只能顺序执行某项任务。不可能“吃着火锅唱着歌”,因为吃饭唱歌都占着嘴呢。所以为了同时并行地处理更多任务,芯片公司开发出了多核架构,只要相互之间没有依赖,每个核心做自己的事情,多核之间互不干扰,就可以达到并行计算的效果,极大缩短计算时间。

CPU vs GPU

个人桌面电脑CPU只有2到8个CPU核心,数据中心的服务器上也只有20到40个左右CPU核心,GPU却有上千个核心。与CPU的核心不同,GPU的核心只能专注于某些特定的任务。知乎上有人把CPU比作大学教授,把GPU比作一个学校几千个小学生:同样是做加减法,几千个小学生所能做的计算,远比几十个大学教授要多得多。俗话说,三个臭皮匠,顶一个诸葛亮。大学教授的知识结构和个人能力远强于小学生,能独立解决复杂问题,小学生的知识有限,只能进行简单的计算。目前来看GPU在处理简单计算任务上有更大的优势,但是主要还是靠人海战术,并不能像CPU那样可以独当一面,短时间内也无法替换掉CPU。如下图所示,在整个计算机系统中,CPU起到协调管理的作用,管理计算机的主存、硬盘、网络以及GPU等各类元件。

计算机体系示意图

如果只关注CPU和GPU,那么计算结构将如下图所示。CPU主要从主存(Main Memory)中读写数据,并通过总线(Bus)与GPU交互。GPU除了有超多计算核心外,也有自己独立的存储,被称之为显存。一台服务器上可以安装多块GPU卡,但GPU卡的发热量极大,普通的空调系统难以给大量GPU卡降温,所以大型数据中心通常使用水冷散热,并且选址在温度较低的地方。

CPU与GPU

GPU核心在做计算时,只能直接从显存中读写数据,程序员需要在代码中指明哪些数据需要从内存和显存之间相互拷贝。这些数据传输都是在总线上,因此总线的传输速度和带宽成了部分计算任务的瓶颈。也因为这个瓶颈,很多计算任务并不适合放在GPU上,比如笔者这两年关注的推荐系统虽然也在使用深度学习,但因为输入是大规模稀疏特征,GPU加速获得的收益小于数据互相拷贝的时间损失。当前最新的总线技术是NVLink,IBM的Power CPU和英伟达的高端显卡可以通过NVLink直接通信。同时,单台机器上的多张英伟达显卡也可以使用NVLink相互通信,适合多GPU卡并行计算的场景。

nvlink

Intel的CPU目前不支持NVLink,只能使用PCI-E技术,如下图所示。NVLink和PCI-E都是总线技术的一种。

CPU通过PCI-E与GPU通信

由于CPU和GPU是分开的,在英伟达的设计理念里,CPU和主存被称为Host,GPU被称为Device。Host和Device概念会贯穿整个英伟达GPU编程。

以上结构也被称为异构计算:使用CPU+GPU组合来加速计算。世界上顶尖的数据中心和超级计算机均采用了异构计算架构。例如超越天河2号成为世界第一的超级计算机Summit使用了9216个IBM POWER9 CPU和27648个英伟达Tesla GPU。

GPU架构

英伟达不同时代产品的芯片设计不同,每代产品背后有一个架构代号,架构均以著名的物理学家为名,以向先贤致敬。当前比较火热的架构有:

  • Turing 图灵

    • 2018年发布
    • 消费显卡:GeForce 2080 Ti
  • Volta 伏特

    • 2017年末发布
    • 专业显卡:Telsa V100 (16或32GB显存 5120个核心)
  • Pascal 帕斯卡

    • 2016年发布

    • 专业显卡:Telsa P100(12或16GB显存 3584个核心)

Pascal架构

在英伟达的设计里,多个小核心组成一个Streaming Multiprocessor(SM),一张GPU卡有多个SM。从“multiprocessor”这个名字上也可以看出SM包含了多个处理器。实际上,英伟达主要以SM为运算和调度的基本单元。上图为当前计算力最强的显卡Tesla V100,密密麻麻的绿色小格子就是GPU小核心,多个小核心一起组成了一个SM。

V100 SM

单个SM的结构如图所示。可以看到一个SM中包含了:

  • 针对不同计算的小核心(绿色小格子),包括优化深度学习的TENSOR CORE,32个64位浮点核心(FP64),64个整型核心(INT),64个32位浮点核心(FP32)。

  • 计算核心直接从寄存器(Register)中读写数据。

  • 调度和分发器(Scheduler和Dispatch Unit)。

  • L0和L1级缓存。

前面提到的以物理学家命名的是英伟达各代GPU的架构代号。对于消费者而言,英伟达主要有两条产品线:

  • 消费级产品 GeForce系列:GeForce 2080 Ti...
  • 高性能计算产品 Telsa系列:Telsa V100、Telsa P100、Telsa P40...

软件生态

英伟达能够在人工智能时代成功,除了他们在长期深耕显卡芯片领域,更重要的是他们率先提供了可编程的软件架构。2007年,英伟达发布了CUDA编程模型,软件开发人员从此可以使用CUDA在英伟达的GPU上进行并行编程。在此之前,GPU编程并不友好。CUDA简单到什么程度?有经验的程序员经过半天的培训,掌握一些基础概念后,能在==半小时==内将一份CPU程序修改成为GPU并行程序。

英伟达软件栈

继CUDA之后,英伟达不断丰富其软件技术栈,提供了科学计算所必须的cuBLAS线性代数库,cuFFT快速傅里叶变换库等,当深度学习大潮到来时,英伟达提供了cuDNN深度神经网络加速库,目前常用的TensorFlow、PyTorch深度学习框架的底层大多基于cuDNN库。英伟达能在人工智能时代击败Intel、AMD等强大对手,很大一部分是因为它丰富的软件体系。这些软件工具库使研发人员专注于自己的研发领域,不用再去花大量时间学习GPU底层知识。CUDA对于GPU就像个人电脑上的Windows、手机上的安卓系统,一旦建立好生态,吸引了开发者,用户非常依赖这套软件生态体系。

GPU编程可以直接使用CUDA的C/C++版本进行编程,也可以使用其他语言包装好的库,比如Python可使用Numba库调用CUDA。CUDA的编程思想在不同语言上都很相似。

CUDA及其软件栈的优势是方便易用,缺点也显而易见:

  1. 软件环境复杂,库以及版本很多,顶层应用又严重依赖底层工具库,入门者很难快速配置好一整套环境;多环境配置困难。

  2. 用户只能使用英伟达的显卡,成本高,个人用户几乎负担不起。

因此,如果没有专业的运维人员维护GPU机器,最好还是在公有云上按需购买GPU虚拟机。入门者可以考虑云厂商的Telsa P4虚拟机,大约10+元/小时,云厂商会配置好CUDA及工具库。如自己购买物理机,可以考虑消费级的GeForce 2080Ti,这张卡足以应对绝大多数自然语言处理任务。

今天获得了一个微形的USB无线网卡,其在Windows下是可以自动安装驱动程序的,因为此USB网卡中包含了Windows版的驱动程序。但是我现在想在Archlinux下使用这个USB无线网卡作为Wifi的发射无线信息供手机上网使用,而windows驱动就无能为力了。通过将USB无线网卡插入到windows电脑上配置好wifi就可以查询到此网卡的具体型号为realtek 8188gu wireless lan 802.11n usb nic linux, 而使用 pacman -Ss无法找到realtek相关驱动,于是使用paru查找:

paru -Ss realtek |grep 8188gu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    Driver for Realtek RTL8188CUS (8188C, 8192C) chipset wireless cards
aur/8188eu-dkms v4.3.0.8_13968-17 [+25 ~0.14]
Driver for Realtek RTL8188EUS (RTL8188EUS, RTL8188ETV) WLAN
aur/8188eu-aircrack-dkms-git 5.3.9.r196.3fae723-5 [+7 ~0.00]
Realtek RTL8188EUS and RTL8188ETV Wi-Fi driver with monitor mode & frame injection support
aur/8188eu-dkms-git 5.2.2.4.r794.53ed527-3 [+5 ~0.00]
Standalone driver for Realtek RTL8188EUS (RTL8188EUS, RTL8188ETV) WLAN
aur/8188fu-kelebek333-dkms-git r115.751882b-1 [+4 ~0.00] [无人维护]
Kernel driver module for Realtek RTL8188FU and RTL8188FTV Wireless 802.11n USB 2.0 adapters (device id 0bda:f179), for kernel versions < 6
aur/8188fu-dkms-git r13.586d795-1 [+3 ~0.00] [无人维护]
Kernel driver module for Realtek RTL8188FU and RTL8188FTV Wireless 802.11n USB 2.0 adapters (device id 0bda:f179), for kernel versions less than 5.8
Kernel module for Realtek RTL8188SU/RTL8191SU/RTL8192SU devices
aur/8188fu-supremegamers-dkms-git r120.40d4a49-1 [+2 ~0.00]
Kernel driver module for Realtek RTL8188FU and RTL8188FTV Wireless 802.11n USB 2.0 adapters (device id 0bda:f179), for kernel versions 6.0 and 6.1
aur/rtl8188gu-dkms-git r10.699d0cc-1 [+2 ~0.00] [已安装:r12.9dec829-1]
Linux driver for Realtek RTL8188GU

rtl8xxxu支持的wifi芯片

1
2
3
4
5
6
7
RTL8188CU/CUS/CTV
RTL8188EU/EUS/ETV
RTL8188FU/FTV
RTL8188GU | RTL8188RU
RTL8191CU | RTL8192CU
RTL8192EU | RTL8192FU
RTL8723AU | RTL8723BU
  • 支持Linux 内核版本: 5.5.x ~ 6.11.x
  • 测试工作良好的Linux发行版:
    • Arch Linux (kernel version: 6.6.39-1-lts)
    • Debian 11.10 (kernel version: 5.10.0-30-amd64 / 6.1.0-0.deb11.21-amd64)
    • Linux Mint 20.3 (kernel version: 5.15.0-113-generic)

安装驱动rtl8xxxu

  1. 克隆下rtl8xxxu的源文件:

    1
    git clone https://github.com/a5a5aa555oo/rtl8xxxu.git

  2. 如果您使用的USB Wifi发射器基于RTL8188GURTL8192FU芯片,您需要首先使用命令usb_modeswitcheject切换到Wifi模式。

  3. 安装构建模块需要的软件包:gcc,make,linux-headers,dkms和其他的依赖软件。

  4. 构建和安装模块

    • 使用传统方式

      make clean modules && sudo make install

    • 使用 DKMS 方式(推荐)

      sudo dkms install $PWD

    • 基于ArchLinux的发行版用户

      AUR中安装软件包: rtl8xxxu-dkms-git

      1
      paru -S rtl8xxxu-dkms-git

  5. 为芯片 RTL8188EU/RTL8188FU/RTL8188GU/RTL8192EU/RTL8192FU 安装固件(关键)

    sudo make install_fw

  6. 载入模块

    sudo modprobe rtl8xxxu_git

安装成功后,可以在设置Wi-Fi中找到网卡 Realtek RTL8188GU,顺利完成无线网卡驱动的安装。

  • 2024年07月17日, 在办公室的台式机90M2CTO1WW (QiTianM428-N000)测试失败,或许是USB版的网卡不稳定?由于时间的关系,不再研究无线wifilinux上的启用。
  • 2024年08月12日, 在办公室的台式90M2CTO1WW(QiTianM428-N000)再次测试,重新安装rtl8188gu-dkms-git之后仍然将网卡识别为光盘,但是执行sudo modprobe rtl8188之后再重启,同时更换的插入的USB接口,发现无线网卡启动成功。
  • 2024年08月13日, 通过rtl8xxxu-dkms官网https://kkgithub.com/a5a5aa555oo/rtl8xxxu安装固件.

安装驱动rtl8xxxu-dkms-git

根据文章RTL8188GU驱动安装, rtl8188gu-dkms-git有已知性能问题,在用户态可以看到一个RTW_CMD_THREAD。我使用时延迟很大并且使用一会就会断开,只能通过拔插恢复。同时也说明rtl8xxxu-dkms-gitLinux主线移植的驱动,性能优异,于是决定卸载rtl8188gu-dkms-git后安装rtl8xxxu-dkms-git

rtl8xxxu驱动安装
1
2
paru -S rtl8xxxu-dkms-git
sudo modprobe rtl8xxxu

如果已经安装了前面的McMCCRU版本的驱动,可以在/etc/modprobe.d/blacklist中添加一行blacklist 8188gu屏蔽旧驱动。

安装 dnsmasq

当安装好rtl8xxxu-dkms-git后,发现使用Gnome自带的开启wifi热成功,但是使用手机连接热点总是失败,同时wifi热点也随之关闭。原因是没有安装dnsmasq,它的主要作用是提供 DNS 缓存和 DHCP 服务功能。作为域名解析服务器(DNS),dnsmasq可以通过缓存 DNS 请求来提高对访问过的网址的连接速度。作为DHCP 服务器,dnsmasq 可以用于为局域网电脑分配内网ip地址和提供路由。DNS和DHCP两个功能可以同时或分别单独实现。dnsmasq轻量且易配置,适用于个人用户或少于50台主机的网络。此外它还自带了一个 PXE 服务器。所以如果没有这个服务,那wifi热点就无法为手机分配 dns, 也就无法启动wifi热点。安装此服务即可:

1
sudo pacman -S dnsmasq

根据文章Arch Linux上简单地创建wifi热点(只用一块无线网卡), 共享wifi需要安装linux-wifi-hotspot, 但是按其配置好我的电脑并不能通过linux-wifi-hotspot共享网络,或许在其文章底部的当然不是用来共享校园网说明了问题。于是果断删除此软件,在安装好dnsmasq之后,使用Gnome自带的创建wifi共享网络成功。