Eric 发布的文章

汇编指令把数据从内存一块区域复制到另外一块区域

最近再次阅读 CSAPP 里面的有关汇编的一节, 它说到了 mov 指令, mov 可以把常量, 寄存器的值, 内存的值移动(move)到寄存器, 内存. 但是不能直接把某个地址内存的值移动到另外一个内存地址. 为什么会有这个限制?

如果考虑到当前的值要做计算, 那么这个值从内存加载到CPU, 然后做计算, 之后再保存到内存. 看上去没啥性能损失, 但是假如只是把某个内存的数据复制(移动)到另外一个内存地址, 那么这个性能损失就很大.

为什么会这么想, 这是因为在 这本书里提到这么一个有关延迟度量的对比:
latency scale.png

可以看到从内存加载, 然后再保存到内存需要的相对时间确实很长, 如果有一个方案直接告诉内存控制器, 把某地址开始的多少数据直接复制到另外一块区域, 那将是飞速提升, 因为只要CPU发出一个内存控制器的指令就可以. 但是这看上去很美, 因为内存控制器并不知道进程虚拟内存这些东西, 它只知道硬件物理内存. 虚拟内存要写的2个连续字节可能在物理内存上是相隔很远的两块地方.

如果一个内存数据并不需要做算术逻辑运算, 那么可以使用专门的字符处理指令 movs, 它有对应不同宽度的: movsb, movsw, movsd, movsq. 它们的使用会涉及到 RSI, RDI, RCX 寄存器, 以及 DF flag, 并且结合 REP 指令来实现连续复制.

但是这里我更关心的是, 这些被移动的字节是不是要到CPU绕一圈? 也就是说这些字节是不是需要占用系统总线(system bus). 根据我互联网上查到的数据, 这些字节通常情况下都需要到 CPU 绕一圈, 涉及到加载(load)和保存(save). 也就是避免不了性能的延迟. 但是如果要移动的数据已经在缓存 (L1, L2, L3), 那么将不需要走系统总线加载. 同时这些加载可能都是成块加载, 所以通常连续的内存区域都会同时被一次加载, 提高了性能. 另外从缓存刷新到内存也是成块(页)刷新到, 那么它也会提高性能.

go语言把部分CPU从 AVX 迁移到 REP movsb - https://github.com/golang/go/issues/66958

有关 memcpy

Linux内核中的memcpy函数是一个用于内存复制的函数,它的主要功能是将一块内存中的数据原封不动地复制到另一块内存中。这个函数在内核编程中非常重要,因为它直接操作内存,效率要求非常高。

声明在 string.h 中
void *memcpy(void *dest, const void *src, size_t n);

为了提高效率,Linux内核中的memcpy通常实现为汇编语言,特别是对于小数据块的复制,会有特别的优化。对于更大的数据块,它可能会利用CPU的特定指令(如SSE/AVX指令集)来提高复制速度。

memcpy 可以根据CPU 的指令集选用 movs 系列的指令, 也可以选择 AVX 或 SIMD 来提高在该架构上的性能.

summary

从内存到内存的复制, 还是需要占用系统总线的.

Linux source 命令是 bash 的 builtin

今天执行一个 shell 脚本, 发现 shell 里面报错: source: not found. 可是去执行 source 命令, 发现又有. 到底怎么回事呢?

原来 source 是 bash 的 builtin 命令, 而我运行这个脚本使用的是 sh script.sh, 它其实使用的是 /usr/bin/sh, 然而在这个 Ubuntu 上面 /usr/bin/sh 其实是指向了 /usr/bin/dash. 然而 source 这个命令在 dash 没有.

supra@suprabox:~$ which sh
/usr/bin/sh
supra@suprabox:~$ which bash
/usr/bin/bash

# 没有找到 source 命令
supra@suprabox:~$ which source

# source 的类型
supra@suprabox:~$ type source
source is a shell builtin

# source 命令存在, 当前我的默认shell 是bash
supra@suprabox:~$ source -h
-bash: source: -h: invalid option
source: usage: source filename [arguments]

supra@suprabox:~$ ls -lah /usr/bin/sh
lrwxrwxrwx 1 root root 4 Mar 31  2024 /usr/bin/sh -> dash
supra@suprabox:~$ ls -lah /usr/bin/bash
-rwxr-xr-x 1 root root 1.4M Mar 31  2024 /usr/bin/bash
supra@suprabox:~$ ls -lah /usr/bin/dash
-rwxr-xr-x 1 root root 127K Mar 31  2024 /usr/bin/dash

Bash 内置的命令执行不会启动另外一个进程, 在当前进程执行.

enable -a 启用所有内置命令, 即查看所有内置命令.

supra@suprabox:~$ enable -a
enable .
enable :
enable [
enable alias
enable bg
enable bind
enable break
enable builtin
enable caller
enable cd
enable command
enable compgen
enable complete
enable compopt
enable continue
enable declare
enable dirs
enable disown
enable echo
enable enable
enable eval
enable exec
enable exit
enable export
enable false
enable fc
enable fg
enable getopts
enable hash
enable help
enable history
enable jobs
enable kill
enable let
enable local
enable logout
enable mapfile
enable popd
enable printf
enable pushd
enable pwd
enable read
enable readarray
enable readonly
enable return
enable set
enable shift
enable shopt
enable source
enable suspend
enable test
enable times
enable trap
enable true
enable type
enable typeset
enable ulimit
enable umask
enable unalias
enable unset
enable wait

git 本地 branch 按照使用时间排序

使用 git 做开发, 我们的 branch 策略是每个新功能, 每个要修改的bug 都在一个单独的 branch 上坐开发. 久而久之, 本地就有很多 branch 了, 有些还没有合并到 master 分支上, 有些是正在开发的本地 branch.

如何查看最近使用的 branch?

# 查看最近使用的 branch 时间先后排序
git branch -l --sort committerdate

# 查看最近使用的 branch 时间倒序排序
git branch -l --sort -committerdate

# 查看最近使用的 branch 时间倒序排序
git for-each-ref --sort=-committerdate refs/heads/

# 查看最近使用的 branch 时间倒序排序 特定格式
git for-each-ref --sort=-committerdate refs/heads/ --format='%(committerdate:iso8601) %(refname:short)'

证书的生成和验证

1. 安装 OpenSSL

首先,确保您的系统上安装了 OpenSSL。在大多数 Linux 发行版中,您可以使用包管理器来安装它。

例如,在 Ubuntu 上:

sudo apt update
sudo apt install openssl

2. 生成私钥

私钥是安全证书的核心,应该保密。使用以下命令生成私钥:

openssl genpkey -algorithm RSA -out private.key -pkeyopt rsa_keygen_bits:2048

这里,-algorithm RSA 指定了使用 RSA 算法,-out private.key 指定了输出文件名,rsa_keygen_bits:2048 设置了密钥长度。

3. 生成 CSR(证书签名请求)

CSR 是发送给证书颁发机构以获取签名证书的请求。生成 CSR 时,系统会提示您输入一些信息,如国家、州、组织等。

openssl req -new -key private.key -out certificate.csr

4. 生成自签名证书

在测试环境中,您可以使用 OpenSSL 生成自签名证书。以下是命令:

openssl x509 -req -days 365 -in certificate.csr -signkey private.key -out certificate.crt

这里,-days 365 设置了证书的有效期限为一年,-in certificate.csr 指定了 CSR 文件,-signkey private.key 指定了用于签名的私钥,-out certificate.crt 指定了输出证书文件。

5. 查看证书信息

您可以使用以下命令来查看证书的内容:

openssl x509 -in certificate.crt -text -noout

可以看到之前的设置内容:

 Signature Algorithm: sha256WithRSAEncryption
Issuer: C=CN, ST=SH, L=Shanghai, O=tianxiaohui.com, CN=10.236.90.154, emailAddress=eric@txh.com
Subject: C=CN, ST=SH, L=Shanghai, O=tianxiaohui.com, CN=10.236.90.154, emailAddress=eric@txh.com

6. 使用证书

生成的 certificate.crt(证书)和 private.key(私钥)可以用于配置 HTTPS 服务器,如 Apache 或 Nginx。

安装证书到 Flask server app

一个基于 Flask 到本地app:

from flask import Flask

app = Flask(__name__)


@app.route('/', methods=['GET', 'POST'])
def index():
    return 'Hello, World!'
# ... 你的应用代码 ...


if __name__ == '__main__':
    # 指定证书和私钥的路径
    app.run(host='0.0.0.0', port=443, ssl_context=('certificate.crt', 'private.key'))

本地访问

本地浏览器访问的到下面的错误:
Your connection is not private
使用 python requests 访问:

requests.exceptions.SSLError: HTTPSConnectionPool(host='10.236.90.154', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1129)')))

非对称加密 RSA

使用 openssl 命令行工具生成RSA公钥和私钥

这个命令将生成一个2048位的RSA私钥,并将其保存在文件 private.pem 中

openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048

以下命令从私钥中提取公钥(public.pem):

openssl rsa -pubout -in private.pem -out public.pem

为什么可以从私钥中提取公钥?

钥文件通常包含了生成密钥对所需的所有信息,包括p、q、n、e和d。因此,可以从私钥文件中提取出n和e,这两者就构成了公钥

使用 python 生成 RSA 公钥和私钥

首先安装 cryptography 包.

 pip install cryptography

以下代码使用 cryptography 来生成 RSA 公钥和私钥.

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# 生成私钥
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)

# 将私钥序列化为PEM格式
pem_private_key = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption()
)

# 从私钥中获取公钥
public_key = private_key.public_key()

# 将公钥序列化为PEM格式
pem_public_key = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

# 打印私钥和公钥
print("Private Key (PEM format):")
print(pem_private_key.decode('utf-8'))

print("\nPublic Key (PEM format):")
print(pem_public_key.decode('utf-8'))

# 你可以将私钥和公钥保存到文件中
with open('private_key.pem', 'wb') as f:
    f.write(pem_private_key)

with open('public_key.pem', 'wb') as f:
    f.write(pem_public_key)

加密和解密

公钥加密消息:

# message.txt 是要加密的原始消息文件,encrypted_message.bin 是加密后的输出文件
openssl rsautl -encrypt -in message.txt -inkey public.pem -pubin -out encrypted_message.bin

私钥解密消息:

# encrypted_message.bin 是加密后的消息文件,decrypted_message.txt 是解密后的输出文件
openssl rsautl -decrypt -in encrypted_message.bin -inkey private.pem -out decrypted_message.txt

签名和验证签名

签名涉及2步, 第一步对消息做哈希,生成 hash值, 第二步对 hash 值做加密(即签名).

python 版本签名.

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key

# 假设你已经有了私钥,这里我们使用之前生成的私钥
# load your private key from local file: private_key.pem
with open('private_key.pem', 'rb') as file:
    pem_private_key = file.read()

# 加载私钥
private_key = load_pem_private_key(pem_private_key, password=None, backend=default_backend())

# 要签名的消息
message = b"this is a message from https://www.tianxiaohui.com"

# 生成消息的哈希值
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(message)
digest_value = digest.finalize()

# 使用私钥对哈希值进行签名
signature = private_key.sign(
    digest_value,
    padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    hashes.SHA256()
)

# 打印签名
print("Signature:", signature)

# 使用公钥验证签名
# public_key = private_key.public_key()
with open('public_key.pem', 'rb') as file:
    public_key = load_pem_public_key(file.read())

try:
    public_key.verify(
        signature,
        digest_value,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print("Signature is valid.")
except:
    print("Signature is invalid.")

openssl 版本

# 生成RSA密钥对
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

# 创建一个文件进行签名
echo "This is a test message." > your_file.txt

# 使用私钥进行签名
openssl dgst -sha256 -sign private_key.pem -out signature.sig your_file.txt
# -sha256 表示使用SHA-256哈希算法。
# -sign private_key.pem 指定私钥文件。
# -out signature.sig 指定输出签名文件的名称。
# your_file.txt 是你要签名的文件。

# 使用公钥验证签名
openssl dgst -sha256 -verify public_key.pem -signature signature.sig your_file.txt

# -sha256 表示使用SHA-256哈希算法。
# -verify public_key.pem 指定公钥文件。
# -signature signature.sig 指定签名文件。
# your_file.txt 是你之前签名的文件。

公钥和私钥的格式

公钥和私钥可以以多种不同的格式保存,这些格式主要分为两大类:非加密格式和加密格式。以下是一些常见的密钥保存格式:

非加密格式

  1. PEM (Privacy Enhanced Mail) 格式:

    • 以文本形式存储,可以包含Base64编码的私钥或公钥。
    • 通常以 -----BEGIN PUBLIC KEY----------END PUBLIC KEY----------BEGIN PRIVATE KEY----------END PRIVATE KEY----- 这样的标记开头和结尾。
  2. DER (Distinguished Encoding Rules) 格式:

    • 二进制格式,通常用于存储证书和密钥。
    • 不以文本形式存储,因此不能直接编辑。
  3. PKCS#1 格式:

    • 主要用于RSA密钥。
    • 可以是PEM编码的文本格式,也可以是DER格式的二进制格式。

加密格式

  1. PKCS#8 格式:

    • 专门用于私钥的存储,可以包含加密的私钥。
    • 可以是PEM编码的文本格式,也可以是DER格式的二进制格式。
    • 如果加密,通常会有 -----BEGIN ENCRYPTED PRIVATE KEY----------END ENCRYPTED PRIVATE KEY----- 标记。
  2. PKCS#12 (PFX) 格式:

    • 用于存储私钥、公钥和证书,通常包含一个密码保护的档案。
    • .pfx.p12 为文件扩展名。
    • 支持二进制格式,可以包含多个密钥和证书。

其他格式

  1. SSH 格式:

    • 专门用于SSH密钥。
    • 私钥通常以 .ssh/id_rsa 文件形式存在,公钥以 .ssh/id_rsa.pub 文件形式存在。
    • 私钥文件通常是PEM格式,但公钥文件是SSH专用的格式。
  2. XML 格式:

    • 少见,但某些应用程序可能会使用XML格式来存储密钥。