乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

C语言中静态库static和动态库shared的区别

发表于 2022-09-28

前言

在c/c++语言编译时 我们经常能碰到关于静态库(static)和动态库(shared)的链接

那它们有什么区别呢

静态库

静态库(static)是程序在编译期间进行链接的, 它会被打包进可执行程序当中

动态库

动态库(shared)也叫共享库, 它是在程序运行是进行链接的, 以独立的文件存在, 不会被打包进可执行程序中, 但是程序在执行时需要能找到这个库的具体位置, 否则程序可能出错

不同系统中动态库的文件类型

系统 动态库后缀 静态库后缀
Linux .so .a
Mac .dylib .a
Windows .dll .lib

两种库的优势和劣势

静态库

优势:

  • 将依赖的静态库直接打包进可执行文件中, 不用担心对方的机器找不到库导致无法运行
  • 由于直接嵌入到了执行程序中 因此运行速度较快

劣势:

  • 会导致可执行文件的体积过大
  • 如果多个程序使用了同一个静态库 一旦静态库出现bug, 那么与之相关的所有可执行程序都要进行重新编译

动态库

优势:

  • 能有效减少可执行程序的体积
  • 一旦库出现bug, 只需修复并重新编译库即可

劣势:

  • 需要将库放到指定路径下 供主程序调用 操作起来相对麻烦
  • 运行速度相对静态库会慢一些

总结

静态库和动态库, 没有孰优孰劣一说, 具体看实际的应用场景, 每种库都有它的用武之地

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

CURL工具使用问题汇总

发表于 2022-09-28

问题1

下载时提示SSL certificate problem:

1
2
3
4
5
6
curl: (60) SSL certificate problem: certificate has expired
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

原因:

这是证书认证缺失导致,可以在请求时关闭ssl证书认证

解决方案:

命令行中加上-k即可, 如:

1
curl -fsSL https://xmake.io/shget.text -k

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

cmake的使用以及语法

发表于 2022-09-28

前言

我们使用 Make 工具构建项目时,需要编写 Makefile,但不同的平台 Make工具是不一样的,比如Linux的GNU Make ,Windows 的 nmake,它们对 Makefile的规范也是不一样的,如果软件要跨平台,则需要针对每一种 Make工具写一份 Makefile,非常浪费时间

而且当软件比较庞大时,Makefile的编写也会变的复杂。

CMake 简介

CMake(Cross-Platform-Make)是一个开源、跨平台的软件构建工具,它使用与平台独立的配置文件来对软件编译过程进行控制,根据用户所需,生成 Makefile 或者IDE的project。

CMake 构建过程

  1. 编写CMake 配置文件CMakeLists.txt,一般放在项目的最顶层目录下

  2. 运行 cmake 命令,参数为 CMakeLists.txt 所在根路径,执行完成后得到 Makefile 文件。

  3. 使用make 命令进行编译。

    1
    2
    3
    mkdir -p build && cd build
    cmake ..
    make

cmake基本语法

  1. 指定最小版本

    如果使用的 CMake版本低于该版本,则会发出致命错误

    1
    cmake_minimum_required(VERSION 3.9)
  2. 设置工程名 可以同时指定工程版本和语言,CXX代表C++

    1
    project(sunflower VERSION 0.0.1 LANGUAGES C CXX)
  3. 单行注释

    1
    # 我是注释
  4. 多行注释

    1
    2
    3
    #[[我是多行注释
    我是多行注释
    ]]
  5. 设置 c/c++版本

    1
    2
    set(CMAKE_C_STANDARD 11)
    set(CMAKE_CXX_STANDARD 11)
  6. 设置编译选项

    1
    2
    set(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS} -g -w -O3")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -w -O3")
  7. 添加宏定义

    1
    add_definitions(-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO)
  8. 设置头文件搜索目录

    实现编译时的 [-I] 选项,设置后引用头文件不需要使用相对路径,直接引用文件名

    1
    2
    3
    4
    5
    include_directories(
    "${PROJECT_SOURCE_DIR}/include"
    "${PROJECT_SOURCE_DIR}/depends/json/"
    "${PROJECT_SOURCE_DIR}/depends/leveldb/include"
    )
  9. 设置库文件搜索目录

    实现编译时的[-L]选项,项目内部明确路径的库文件,可以通过该命令指定

    1
    2
    3
    4
    link_directories(
    "${PROJECT_SOURCE_DIR}/depends/json/lib"
    "${PROJECT_SOURCE_DIR}/depends/leveldb"
    )
  10. 查找指定库文件

    查找到指定的预编译库,并将它的路径存储在变量中。

    默认的搜索路径为 cmake 包含的系统库,因此如果是NDK的公共库只需要指定库的name 即可(不需path),类似的命令还有find_file()、find_path()、find_program()、find_package()

    1
    find_library(log-lib,log)
  11. 查找指定目录下源文件
    获取指定目录下源文件列表,保存到 DIR_SRCS 变量中,后续编译构建目标可执行文件

    注意这里不能递归获取子目录下的源文件

    1
    aux_source_directory(${PROJECT_SOURCE_DIR}/src DIR_SRCS)
  12. 添加子目录源文件
    使用命令 add_subdirectory 指明本项目包含一个子目录,这样子目录下的 CMakeLists.txt文件和源文件也会被处理。

    项目包含多个子目录时,通常可以在子目录下也定义CMakeLists.txt 文件,并通过 add_library 生成包含子目录源码的库文件,并最终链接到目标可执行文件

    1
    2
    add_subdirectory(utils)
    add_subdirectory(config)
  13. 递归查找目录下的所有源文件
    可以通过 file命令递归获取指定目录下的所有源文件,这样可以不用为子目录专门定义CMakeLists.txt,也不需要通过链接的方式集成各个子目录,而是统一编译构建

    1
    2
    3
    4
    5
    file(GLOB_RECURSE DIR_SRCS
    ${PROJECT_SOURCE_DIR}/src/*.c
    ${PROJECT_SOURCE_DIR}/src/*.cc
    ${PROJECT_SOURCE_DIR}/src/*.cpp
    )
  14. 设置可执行文件输出路径

    1
    set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
  15. 生成可执行文件

    1
    add_executable(${PROJECT_NAME} ${DIR_SRCS})
  16. 生成库文件

    1
    2
    add_library(${PROJECT_NAME} STATIC ${DIR_SRCS})
    add_library(${PROJECT_NAME} SHARED ${DIR_SRCS})
  17. 链接库文件

    1
    2
    3
    4
    5
    6
    7
    set(EXTRA_LIBS
    sasl2
    libleveldb.a
    )
    target_link_libraries(${PROJECT_NAME}
    ${EXTRA_LIBS}
    )

常用变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PROJECT_SOURCE_DIR:工程的根目录

PROJECT_BINARY_DIR:运行cmake命令的目录,通常为${PROJECT_SOURCE_DIR}/build

PROJECT_NAME:返回通过 project 命令定义的项目名称

CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径

CMAKE_CURRENT_BINARY_DIR:target 编译目录

CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径

EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置

LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

自定义变量

使用SET, 比如:

1
2
3
4
5
6
7
8
set(BINARY_NAME "juce_jni")

add_library( ${BINARY_NAME}

SHARED

"../../../Source/Main.cpp"
)

支持 Debug 版本

  1. 设置不同版本的编译选项

    1
    2
    set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
    set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
  2. cmake 时指定版本

    1
    2
    cmake .. -DCMAKE_BUILD_TYPE=Debug
    cmake .. -DCMAKE_BUILD_TYPE=Release

完整示例

工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
+-- CMakeTest
|
+-- CMakeList.txt
+-- src
|
+-- main.cc
+--- utils/
|
+--- utils.cc
+--- utils.h
+-- config/
|
+-- config.cc
+-- config.h
+-- libs
|
+-- libleveldb.a

CMakeList.txt

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
cmake_minimum_required(VERSION 3.7.1)

project(CMakeTest VERSION 0.0.1 LANGUAGES C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -w -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -w -O3")

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

include_directories(
"utils"
"config"
)

link_directories(
""${PROJECT_SOURCE_DIR}/libs""
)

# 子目录的CMakeList.txt也可以定义到主配置中
# 将utils和config两个子目录的源文件打包成库test_share
aux_source_directory(${PROJECT_SOURCE_DIR}/src/utils SHARE_SRC_FILES)
aux_source_directory(${PROJECT_SOURCE_DIR}/src/config SHARE_SRC_FILES)
add_library(test_share STATIC ${SHARE_SRC_FILES} )

aux_source_directory(${PROJECT_SOURCE_DIR}/src DIR_SRCS)
add_executable(${PROJECT_NAME} ${DIR_SRCS})

# 也可以使用file命令递归获取目录下所有源文件
file(GLOB_RECURSE DIR_SRCS
${PROJECT_SOURCE_DIR}/src/*.c
${PROJECT_SOURCE_DIR}/src/*.cc
${PROJECT_SOURCE_DIR}/src/*.cpp
)
add_executable(${PROJECT_NAME} ${DIR_SRCS})

set(EXTRA_LIBS
ldl
pthread
libleveldb.a
)

target_link_libraries(${PROJECT_NAME}
test_share
${EXTRA_LIBS}

)

本文转载自: CSDN

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

VST音频插件的开发

发表于 2022-09-27

关于

JUCE是一个开源的跨平台 C++ 应用程序框架,适用于桌面和移动应用程序,包括VST、VST3、AU、AUv3、RTAS 和 AAX音频插件。

GITHUB地址

JUCE官网

开发前的准备工作

  1. 下载juce

    image-20220928095652701

    点击进入下载页面

  2. 解压启动Porjucer, 并创建新工程

    image-20220928095935785

    image-20220928101600349

  3. 使用本地IDE编辑插件源码

    Mac平台支持Xcode,windows平台支持visual studio

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

使用Clang对lua源码进行交叉编译

发表于 2022-09-27

什么是交叉编译

在一个平台上编译生成其他平台可运行的程序, 我们称之为交叉编译

以c语言为例, 在Mac平台编译出来的程序只能在Mac平台运行, 将其拷贝到Windows平台则无法识别, 解决这个问题的办法就是将源代码移至Windows平台 然后重新编译一次 这种做法属于原始的常规编译

那能不能在Mac平台编译生成能在Windows平台运行的程序呢, 可以, 只要借助交叉编译工具即可实现, 而这种非常规编译手段也叫做交叉编译

关于交叉编译实操, 详见之前的文章《android设备上如何运行C语言原生程序》

针对C/C++语言目前主流的交叉编译有:

  • GCC: GNU旗下的一款编译工具
  • CLANG: 苹果主导编写的一款基于LLVM的编译工具
  • MSVC: 微软旗下的一款编译工具

为什么使用Clang

相较之下, Clang编译速度更快, 占用内存更小, 错误提示更加人性化

Clang常规使用

我们先以一个C语言小程序来示范clang的编译流程

  1. 编写c程序代码 文件名为main,c

    1
    2
    3
    4
    5
    6
    7
    #include <stdio.h>

    int main(int argc, char** argv){
    printf("Hello World!\n");

    return 0;
    }
  2. 编译程序源码为当前平台可执行文件

    1
    clang main.c -o main

    -o:指定输出执行文件路径和程序名称

  3. 编译时顺带打印编译日志

    添加一个-v即可

    1
    clang main.c -o main -v
  4. 编译生成预处理文件

    使用-E, 区分大小写

    1
    clang -E main.c -o main
  5. 编译生成汇编代码

    使用-s, 区分大小写

    1
    clang -S main.c -o main
  6. 编译生成obj文件

    使用-c, 区分大小写

    1
    clang -c main.c -o main

Clang交叉编译

  1. 指定cpu架构

    使用-arch, 比如我们要编译生成arm64位架构平台可执行的程序

    1
    clang  main.c -o main -arch arm64

    正常情况下 上述指令会执行失败, 那是因为clang默认会使用本平台的SDK进行链接编译, 而本平台的SDK如果不支持arm的话自然无法编译 目前大部分电脑用的cpu依然还是x86架构的

    既然如此 倘若我们需要编译能在iPhone上运行的程序, 那么就需要指定可以支持arm的SDK来进行编译, 比如iphoneOS_SDK

  2. 指定编译SDK

    在clang中, 通过来-isysroot指定编译SDK, 如下:

    1
    clang -isysroot  /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.5.sdk main.c -o main -arch arm64

    编译成功后 生成的可执行文件即可在苹果手机上运行

  3. 指定操作系统

    1
    clang  main.c -o main -target x86_64-apple-darwin-eabi

    这里使用-target同时指定cpu架构和操作系统,-target的参数一共分为四部分:

    • arch:cpu架构 比如arm,x86_64
    • vendor: 工具连提供厂商 比如pc, apple,nvidia,ibm,等
    • os: 操作系统 比如darwin,linux ,win32
    • abi:应用程序二进制接口, 描述了程序在目标平台的运行规则, 比如eabi, androidabi ,gnueabi等

    每部分用横杆隔开, 合起来就是arch-vendor-os-abi

示例

  1. Mac编译android平台程序

    这里我使用的是ndk包下的clang进行编译

    1
    ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang --target=armv7-none-linux-androideabi16 --sysroot=/Users/songjian/Library/Android/sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/darwin-x86_64/sysroot main.c -o androidExc
  2. Mac编译iphone平台程序

    1
    clang -isysroot  /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.5.sdk main.c -o main -arch arm64

lua源码交叉编译

接下来 我们在Mac平台上编译生成android上可执行的lua程序

由于lua源码文件比较多, 手敲命令编译太过繁琐, 因此我这里使用gradle+cmake构建工具进行快速编译

其中build.gradle配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apply plugin: 'c'
apply plugin: 'com.android.application'

android {
compileSdkVersion 30
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
defaultConfig {
//需要限制最小库版本不能太低 不然有些c库可能缺失
minSdkVersion 21
targetSdkVersion 30

}

}

这里 我将所有lua源码放置在了lua目录当中

CMakeLists.txt配置如下:

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
# 限定cmake最小版本号 当前使用的版本必须在这个之上
cmake_minimum_required(VERSION 3.10.2)

# 描述工程名称
project("cproject")

# 自定义变量 使用${变量名}进行引用
set(EXEC_LUA "lua")
set(EXEC_LUAC "luac")

#获取指定目录下源文件列表,保存到 `DIR_SRCS` 变量中
aux_source_directory(${PROJECT_SOURCE_DIR}/src/main/c/lua DIR_SRCS )

#lua有两个主程序 单独抽出来
set(LUA_MAIN ${PROJECT_SOURCE_DIR}/src/main/c/lua/lua.c)
set(LUAC_MAIN ${PROJECT_SOURCE_DIR}/src/main/c/lua/luac.c)


#排除文件
list(REMOVE_ITEM DIR_SRCS ${PROJECT_SOURCE_DIR}/src/main/c/lua2/luac.c)
list(REMOVE_ITEM DIR_SRCS ${PROJECT_SOURCE_DIR}/src/main/c/lua2/luac.c)


# 生成可执行文件
add_executable(${EXEC_LUA}
${DIR_SRCS} ${LUA_MAIN})

add_executable(${EXEC_LUAC}
${DIR_SRCS} ${LUAC_MAIN})

编译工程后我们在build目录可以查看到生成的程序:

image-20220929105030969

将其push到root过的android设备上即可正常运行

关于工程配置, 如有疑问 可参考文章《使用Android Studio进行C/C++的开发》

附加

  1. Mac平台上查看可执行文件的架构

    1
    file 程序名 或 lipo –info 程序名
  2. 如果你的电脑安装了xcode, 那么可以使用xcrun来查看sdk路径

    查看当前平台SDK路径:

    1
    xcrun --show-sdk-path

    查看iphone平台SDK路径:

    1
    xcrun --sdk iphoneos --show-sdk-path

    甚至可以用xcrun配合clang简化编译指令:

    1
    2
    3
    xcrun -sdk iphoneos clang -arch arm64 -o main main.c
    或者
    clang -isysroot `xcrun --sdk iphoneos --show-sdk-path` main.c -o main -arch arm64

    以及本机平台

    1
    clang -isysroot `xcrun --show-sdk-path` main.c -o main -arch x86_64
  3. 目前iphoneOS_SDK支持的cpu架构有

    1
    2
    3
    4
    arm64
    armv7s
    armv7
    armv6

    x86系列处理器暂不支持

总结

关于GCC和Clang的使用对比:

GCC 会针对每一个编译主机和目标架构提供一套完整的套件,包含了二进制、头文件和库等。所以通常使用起来比较简单

而Clang 是复用一套编译系统去负责多个目标的编译任务,通过 -target选项来区分, 各个平台对应的头文件和库需要自己单独准备 编译的时候将路径通过参数告知给Clang, 使用起来相对会麻烦一些

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

iOS逆向篇-Clang交叉编译

发表于 2022-09-27

本篇我们的目标是在IPHONE上运行ARM64,C语言,C++和OC程序!

  1. Clang
    对于iOS开发者来说,Clang编译器一点也不陌生,Clang是一个C语言、C++、Objective-C、C++语言的轻量级编译器。源代码发布于BSD协议下,是基于LLVM的,也是Xcode 第一的编译器。
  2. 交叉编译
    指在一个平台上生成另一个平台上的可执行代码。现在我们就在Mac上写代码,在iPhone上运行,想想都刺激!
  3. 设备环境
    使用的是Mac和越狱iOS13.2.2的iPhoneX

ARM64

  1. 新建一个hello.txt文本
  2. 把下面代码复制进去,保存退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.extern _printf
.align 4

.text
.global _main

_main:

stp x29, x30, [sp,#-0x10]! ;保存 x29 和 x30 寄存器到栈,!用来控制基址变址寻址的最终新地址是否进行回写操作
mov x29, sp ;将 sp 寄存器放入 x29 寄存器
sub sp,sp,#0x20 ;分配栈空间

adr x0,msg ;第一个参数
bl _printf

add sp,sp,#0x20 ;释放栈空间
mov sp,x29 ;将 x29 给 sp
ldp x29,x30,[sp],0x10 ;出栈给 x29 和 x30

ret ;返回

msg:
.asciz "Hello,world!\n" ; data段有一个标号msg,代表字符串"Hello,world!\n"的首地址,类型为.asciz的字符串
  1. 改文本后缀,改为hello.asm文件
  2. 打开终端,使开始编译
1
2
3
4
5
6
7
8
9
1、查看sdk安装路径
$xcrun --sdk iphoneos --show-sdk-path

2、clang编译
clang -arch arm64 -isysroot "sdk" -o hello hello.asm

// "-arch arm64" 指定框架
// " -isysroot" 指定sdk
// "-o" 输出目标文件
  1. 签名,在iOS系统中运行程序需要代码签名,所以要增加一个签名步骤。
    a.新建ent.plist
    b.把下面代码复制进去,保存在hello.asm同一目录下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>platform-application</key>
<true/>
<key>com.apple.springboard.debugapplications</key>
<true/>
<key>run-unsigned-code</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>task_for_pid-allow</key>
<true/>
</dict>
</plist>
12345678910111213141516
  1. 对hello文件进行签名
1
2
codesign -s - --entitlements ent.plist -f hello
1

在这里插入图片描述

  1. 把签名后的hello文件拷进iPhone的/usr/bin/目录下,好像很多目录下都可以的
1
2
scp hello root@192.168.0.170:/usr/bin
1

在这里插入图片描述)在这里插入图片描述

  1. 运行hello文件,完美!

在这里插入图片描述)在这里插入图片描述

———————-华丽的分割线————————

C语言

  1. 新建1.txt
  2. 复制代码保存,文件改为1.c
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main()
{
printf("C:Hello, World! \n");

return 0;
}
12345678
  1. 编译签名

在这里插入图片描述

  1. 拷贝到iPhone上,运行, 完美!

在这里插入图片描述)img

———————-华丽的分割线————————

OBJECT-C

  1. 新建2.txt
  2. 复制代码保存,文件改为2.m
1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"OC:Hello World!");
}
return 0;
}
123456789
  1. 编译签名
    oc的编译有点不一样,因为导入了foundation框架,在Clang编译的时候需要指定一下
1
2
clang -arch arm64 -framework Foundation -isysroot "sdk" 2.m -o 2
1

在这里插入图片描述

  1. 拷贝到iPhone上,运行, 完美!

在这里插入图片描述)img

———————-华丽的分割线————————

C++

  1. 新建3.txt
  2. 复制代码保存,文件改为3.cpp
1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
int main()
{
cout << "C++:Hello, world!" << endl;
return 0;
}
1234567
  1. 编译签名
    因为clang完美兼容g++,gcc,所以一些命令可以完美继承!
    在这里插入图片描述
  2. 拷贝到iPhone上,运行, 完美!

在这里插入图片描述)img
———————-华丽的分割线————————

好了,今天的代码就敲到这里了,更多Clang的用法可以去看看文档。
文档:http://clang.llvm.org/docs/UsersManual.html

本文转载自: CSDN

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

使用Gradle将lua源码构建编译成可执行文件

发表于 2022-09-26

前言

对于lua的构建 官方默认使用make工具, 常见的构建工具有:

  • Make:又指GNU Make, 是Linux系统下的通用构建工具, 延伸的构建工具还有nmake, dmake, cmake, xmake等等
  • Scons: 以python语言编写的一款跨平台构建工具 对标make 容易学习快速上手
  • Ant: 基于java的构建工具, 被认为是make工具的替代品, 早期eclipse采用的构建工具就是它
  • maven: 同样是基于java, 它不单单是构建工具 还是项目管理工具, 属于Ant进化版, 目前Idea平台中 java的构建基本用的就是它
  • Gradle: 基于Groovy的构建工具, 被认为是Ant和Maven的替代品, 目前Android编译使用的构建工具就是它

什么是构建工具

以java程序为例, 我们在写好代码后, 需要使用javac指令对源码进行编译, 然后使用java指令执行

这个过程叫做编译, 如果一两个文件还好, 代码文件一多, 手动敲指令进行编译是会死人的🤪, 于是乎自动化的构建工具出现了, 目的就是为了取代人工编译

构建工具的发展

早期的make–>java的ant–>后来的maven–>现在的gradle

每一代构建工具的诞生, 都是为了解决上一代工具存在的痛点

Gradle, 用过的都说好!!😆

对Lua源码进行构建编译

我这边直接使用Android Studio, 具体步骤如下:

  1. 首先 去lua官网下载源码, 这里我使用的是5.4的源码

    lua官网

    image-20220926170825041

  2. 然后 在Studio中新建一个空工程, 然后将lua源码拖进src目录中

    image-20220926163907555

  3. 紧接着 对build.gradle文件进行配置

    lua一共包含两个可执行文件, 一个是lua另一个是luac, 所以这两个执行文件我们需要分开构建, 具体的gradle配置如下:

    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
    apply plugin: 'c'  //使用c构建模式


    model {

    components {
    //创建一个lua执行文件构建入口
    lua(NativeExecutableSpec) {
    sources {
    c {//指定源码位置以及需要包含和排除的问价
    source {
    srcDir "src/main/c/lua"
    include '*.c'
    exclude 'luac.c'
    }
    exportedHeaders {
    include 'lauxlib.h', 'lua.h', 'lua.hpp', 'luaconf.h', 'lualib.h'
    }
    }
    }

    }
    //创建一个luac执行文件构建入口
    luac(NativeExecutableSpec) {
    sources {
    c {
    source {
    srcDir "src/main/c/lua"
    include '*.c'
    exclude 'lua.c'
    }
    exportedHeaders {
    include 'lauxlib.h', 'lua.h', 'lua.hpp', 'luaconf.h', 'lualib.h'
    }
    }
    }
    }

    }

    }
  4. 最后 开始编译
    gradle刷新同步后, 我们在右侧gradle任务面板可以看到多出lua和luac相关的task, 此时我们执行build任务开始对lua源码进行编译:

    image-20220926164804018

    编译完成后, 我们可以在build文件中查看到生成的可执行文件:

    image-20220926165101437

    双击可正常运行:

    image-20220926165135047

    至此 我们的lua编译完毕

附加

  1. 如果你只想编译lua而不编译luac, 那么可以单独运行luaExecutable:

    image-20220926172451757

  1. 在gradle配置文件中的组件的名称可以自定义

    image-20220926172714010

关于编译工具

编译工具和构建工具是两个不同的东西:

  • C/C++/Object-C语言常用的编译工具有: GCC,Clang

  • Java语言的编译工具为Javac, 当然GCC也能编译java语言

  • Android的编译工具为aapt,so库的编译早期采用的是GCC, 现在是Clang

上面对于lua的编译, 由于我测试时使用的是Mac平台, 同时电脑还装了Xcode, 所以内部默认采用的编译器是clang

关于Clang和GCC的介绍, 可以参见之前的文章《xcode中三种编译器的区别》

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Linux 系统下使用 systemd 控制 frps 及配置开机自启

发表于 2022-09-22

操作

这个示例将会演示在 Linux 系统下使用 systemd 控制 frps 及配置开机自启。

在 Linux 系统下,使用systemd 可以方便地控制 frp 服务端 frps 的启动和停止、配置后台运行和开启自启。

要使用 systemd 来控制 frps,需要先安装 systemd,然后在 /etc/systemd/system 目录下创建一个 frps.service 文件。

  1. 如Linux服务端上没有安装 systemd,可以使用 yum 或 apt 等命令安装 systemd。

    1
    2
    3
    4
    # yum
    yum install systemd
    # apt
    apt install systemd
  2. 使用文本编辑器,如 vim 创建并编辑 frps.service 文件。

    1
    $ vim /etc/systemd/system/frps.service

    写入内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [Unit]
    # 服务名称,可自定义
    Description = frp server
    After = network.target syslog.target
    Wants = network.target

    [Service]
    Type = simple
    # 启动frps的命令,需修改为您的frps的安装路径
    ExecStart = /path/to/frps -c /path/to/frps.ini

    [Install]
    WantedBy = multi-user.target
  3. 使用 systemd 命令,管理 frps。

    1
    2
    3
    4
    5
    6
    7
    8
    # 启动frp
    systemctl start frps
    # 停止frp
    systemctl stop frps
    # 重启frp
    systemctl restart frps
    # 查看frp状态
    systemctl status frps
  4. 配置 frps 开机自启。

    1
    systemctl enable frps

本文转载自: gofrp文档

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

supervisor和systemd的对比

发表于 2022-09-21

前言

过去我们项目组的应用都是用 supervisord 托管的。最近因为某些因素,无法使用 supervisord,因此考虑改用 systemd。

systemd作为主流 Linux 发行版的默认选项,之前多多少少用过一点 systemd。不过这次需要上生产环境,所以抽空深入研究一番。

为什么要用supervisord?

  1. 实现进程的分组管理,比如支持一同启动/停止多个生产者/消费者实例。
  2. 进程崩溃的时候可以重启

要想改用 systemd,需要看下systemd 如何应对这两个问题。
(如无指明,在本文中,supervisord 的配置项在 [program:x] 下面,而 systemd 的配置项则位于 [Service])

进程控制

无论 supervisord 还是 systemd,都采用 ini作为配置文件的格式。跟 supervisord 不同的是,systemd 每个程序都要单独开一个unit文件。

supervisord 可以同时启动/停止配置文件中所有的进程(或者某个进程组配置中的进程)。systemd可以用依赖来实现这一点。下面例子中,我们
就创建了一个可以同时管理的进程组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
; group.target
[Unit]
Description=Application
Wants=prog1.service prog2.service
; prog1.service
[Unit]
Description=prog1
PartOf=group.target

[Service]
ExecStart=/usr/bin/prog1
Restart=on-failure
; prog2.service
[Unit]
Description=prog2
PartOf=group.target

[Service]
ExecStart=/usr/bin/prog2
Restart=on-failure

systemctl start group.target,prog1 和 prog2 也会带起来。systemctl restart group.target,prog1 和 prog2 也会跟着重启。

相对来说,supervisord 的做法更加直观一些。

如果要更改supervisord的配置文件,supervisord 需要运行 supervisorctl reread 才会生效。
而 systemd 则需要 systemctl daemon-reload。半斤八两吧。

不过 supervisord 有一个好。如果你不知道哪些程序的配置改变了,简单地执行 supervisorctl update,所有涉及的进程都会被重启。
而 systemd 貌似做不到这一点。

systemd 可以指定 stop 操作时可以选择的命令(ExecStop=)。另外它还提供了 ExecReload=,可以自定义调用 systemctl reload xxx 重新读取程序配置文件时的操作。
supervisord不支持 reload 指定进程。同时对于 stop操作,它只允许你选择要发送哪种信号…

supervisord 的 stopwaitsecs 可以控制stop 操作后等待程序退出的耐心(以秒衡量)。待给定的耐心都消耗完毕后,supervisord 才会痛下杀手,发送 SIGKILL。

systemd 对应的配置项是 TimeoutStopSec=。systemd 会给多一次机会,在下最后通牒之前,会先发送 SIGTERM,过另一个TimeoutStopSec之后才发送 SIGKILL。

进程重启

为了避免在supervisord 和 systemd两套术语间迷糊,请允许我抛弃所有术语,用自己的话描述。
life-cycle

从上图可以看到,进程在 RUNNING 之前,会有一个 STARTING 的过程。在 STARTING 过程中,进程可能会读取配置文件,进行初始化。
这一过程中的错误处理,跟 RUNNING 状态的应当有所不同。
以上是supervisord的想法。

所以supervisord 提供了单独的 startretries 配置项,用来配置 STARTING 阶段的重启次数。
systemd 对此没有特殊处理。

一个程序,从 RUNNING 到 EXITED,有两种可能:正常退出或异常退出…(废话)
这两种情况,是通过配置的退出码来区分的。对于 supervisord,这个配置项是 exitcodes。systemd 则通过 SuccessExitStatus 来控制。
有趣的是,exitcodes 的默认值是 0,2,不知道为何它会认为 2 也是正常的退出码。

如果配置了 autorestart = true,只要程序退出,supervisord 都会把它启动起来。相对的,如果配置的是 autorestart = unexpected,则只有
异常退出才会重启。这两个选项,在 systemd 里对应 Restart=always 和 Restart=on-failure。systemd 还提供了 Restart=on-success(只有正常
退出才重启)和 Restart=on-abort(只有收到异常信号才重启)。

对于重启次数,supervisord 没有作限定。因为重启一个程序时,supervisord 会先让它处于 STARTING 状态。这个状态的持续时间,是由配置项
中的 startsecs 决定的,默认 1 秒。如果是不可恢复的错误,程序就不可能成功进入到 RUNNING 状态。当然也许存在这样的情况,程序运行 1 秒
后,就会崩溃。那么它就会陷于不停重启的无间地狱。

systemd 对此一如既往,提供了 N 多选项以供采用。你可以用 RestartSec 控制每次重启的间隔,可以用 StartLimitInterval 和 StartLimitBurst 设定
给定周期内能够重启的次数。比如指定 StartLimitInterval=1s,StartLimitBurst=3,就可以实现跟 supervisord 一致的默认重启策略。

比较完最基本的两种功能,让我们继续看看,两者在一些小细节上的对对碰。

控制实例数

supervisord 可以用 numprocs 来控制单个程序对应的实例数。systemd 也可以做到这一点,虽然有点麻烦(某种意义上,更加强大)。
systemd 会把以 @ 结尾的 service 文件当作模板,在运行时根据给定的参数展开成多个实例。

具体实现方式见:http://0pointer.de/blog/proje…

日志

supervisord 能够重定向被托管的程序的 stdout 和 stderr 到日志文件中,并提供日志切割服务。systemd 也支持这一点,尽管它的实现有很大的不同。

根据鄙人的经验,基于定期检查的日志切割服务,不是个好的选择。
一旦遇上突发高峰,有可能会出现日志无法及时切割的情况;而调小检查间隔,大部分情况下都在无意义地空转。(说的就是你,logroated)
好在无论是 supervisord,还是systemd,提供的切割服务都是实时的。每当写入内容会超过上限时,就会自动切割。

systemd 的日志服务是通过 journald 组件实现的。你可以在 /etc/systemd/journald.conf 中配置它。
journald 默认的日志存储形式是 Storage=auto。这个选项比较奇妙,如果你创建了 /var/log/journal 文件夹,那么它就会把日志写到这个文件夹下。否则不进行持久化。

持久化后的日志是这个样子的:

1
2
3
4
/var/log/journal/c4010ceea79847afbedecb60a775db96/
├── system.journal
├── user-1000.journal
└── user-65534.journal

第一次看到这样的目录结构,说不定你会大吃一惊。journald 设计者脑洞不是一般的大。从这个结构上,根本看不出应用日志在哪里嘛。
不,完全没有这样的必要,因为所有的程序的日志都会写到一块去。不分彼此,全变成一团浆糊。随便一提,日志默认都是压缩的。

要看日志,你得用 journalctl。比如看 prog1.service 的日志,需要 journalctl -u prog1.service。要看特定时期的日志,需要 journalctl --since $timestamp --until $timestamp。

这么前卫的设计我可接受无能。这种 journalctl 控制一切的方式,导致 systemd 日志无法集成到传统的日志收集工具中。
程序员工具箱中各种 text base 处理工具,对此也大眼瞪小眼,只能对着 journalctl 低三下四,接受对方的小脾气。

journald 提供了三个配置项,RuntimeMaxFileSize= 和 RuntimeMaxFiles=。顾名思义,就是单个日志文件大小和允许的日志数。
另外,RuntimeMaxUse= 和 RuntimeKeepFree= 可以控制总大小的上限。

supervisord 在这方面做的要好得多。通过 stdout/stderr_logfile_maxbytes 和 stdout/stderr_logfile_backups,你可以规划每一个程序的日志文件的切割粒度。
不同程序的日志不会挤一起,产生日志少的程序也不会被产生日志多的程序干扰。

开机自启

systemd 支持开机自启, 而supervisor开机自启需依赖其他程序实现, 其本身也是被监控的对象

systemd vs supervisord

除了以上几点外,还有一些没有具体提到的功能。
比如 supervisord 通过 priority 配置进程启动顺序,以及 systemd 对应的 Before/After 依赖机制。
比如 supervisord 的 events 功能,和与之相对应的 systemd 的 notify 机制。
比如 supervisord 可以管理 fastcgi(真有人这么做吗)。
比如 systemd 提供的基于 cgroup 的资源限制。
由于没有使用经验,对这些功能就不作一一比较了。

总结

systemd和 supervisord 各有长短,不存在哪一方绝对的碾压。

systemd 跟Linux 紧密结合,所需的依赖少,其提供的保障自然比 supervisord 更可靠。然而在强大的能力背后,也有配置复杂、不易上手等问题。

supervisord偏于应用层,却因此有独特的用武之地。

举个例子,许多人会往 docker打包里面封入一份supervisord,让它来做 PID 1,以此稍微增强下健壮性。
换 systemd 做同样的事,就像用园艺剪刀裁纸,即使能够顺利完成,也难免事倍功半。毕竟这样的方式跟systemd 的设计是背道而驰的。

本文转载自: segmentfault

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

如何使用supervisor实现内网穿透持久化

发表于 2022-09-21

前言

使用内网穿透时 可能会出现各种不稳定因素导致服务中断, 一旦中断 那么外网就无法对内网进行访问了

为了保证连接的持久稳定性, 我们需要对进程进行监控, 一旦发现服务掉线或者出错 就自动重启程序

为了实现这个需求, 我们可以用supervisor, 它是由python写的一个进程管理工具, 主要功能有:

  • 启动、重启、关闭进程
  • 服务挂掉后,自动重启
  • 可执行文件或者配置文件修改后,服务自动重启
  • 内置可视化界面管理

上一章给大家介绍了《使用frp进行内网穿透实现外网访问局域网中的服务器》

由于服务端使用的是第三方的中转服务器, 服务器的稳定性暂不考虑, 目前只需关心客户端这一块, 这里 我以内网中Mac机器为例给大家介绍supervisor的使用

开始实现

  1. 安装supervisor

    1
    brew install supervisor

    或者

    1
    sudo pip3 install supervisor
  2. 检查是否安装成功

    1
    brew info supervisor

    或者

    1
    brew info supervisor

    显示版本号则说明安装成功

  3. 创建配置文件

    在/usr/local/etc/目录下创建一个配置文件名为supervisord.conf

    1
    touch supervisord.conf
  4. 配置服务

    在配置文件中添加以下内容

    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
    [inet_http_server]         ;开启http服务 
    port=127.0.0.1:9001 ;指定端口号为9001
    username=user ; 用户名
    password=123 ; 密码



    [supervisord]
    logfile=/tmp/supervisord.log ; 日志文件
    logfile_maxbytes=50MB ; 日志文件最大50Mb
    logfile_backups=10 ; 日志备份数量
    loglevel=info ; 打印日志的等级 默认为info 除此之外还有 debug,warn,trace
    pidfile=/tmp/supervisord.pid ;pid文件路径
    nodaemon=false ; 是否在前台运行
    silent=false ;是否关闭日志打印
    minfds=1024
    minprocs=200


    [supervisorctl]
    serverurl=http://127.0.0.1:9001 ;访问这个服务进行进程管理
    username=user ; 需要和http用户名保持一致
    password=123 ; 需要和http密码保持一致 用于管理工具的连接 如果不配置的话 每次使用supervisorctl都得手动输入用户名密码


    [rpcinterface:supervisor] ;这个配置必须要有
    supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

    配置详解:

    • [inet_http_server]: 开启http服务 提供可视化监控界面
    • [supervisord]:supervisor服务配置 缺少它supervisor无法启动
    • [supervisorctl]:supervisor管理工具
  1. 启动supervisor

    1
    supervisord -c /usr/local/etc/supervisord.conf

    启动后 我们在浏览器中输入http://127.0.0.1:9001, 填入配置文件中的username和password, 进入到守护进程管理页面

    image-20220921152026301

    我们在页面中为发现进程信息, 因为还没有配置需要进行守护的进程

  2. 添加需要进行监测并守护的进程

    在配置文件中追加以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [program:frpc] ;名称为frpc  这个可以自定义 但是不能重复
    command=/Users/songjian/frp/frpc/frpc -c ./frpc.ini ; 需要运行的程序指令
    directory=/Users/songjian/frp/frpc ;程序目录
    autostart=true ; supervisor 启动时跟随启动
    autorestart=true ; 程序崩溃时自动重启
    startsecs=5 ; # of secs prog must stay up to be running (def. 1)
    startretries=3 ;重试次数
    stdout_logfile=/Users/songjian/frp/frpc/supervisord.log ; 日志文件路径
    stdout_logfile_maxbytes=20MB ; 日志文件最大容量
    stdout_logfile_backups=10 ; # 日志备份数量
  3. 重载配置文件

    1
    supervisorctl

    执行后提示需要验证 输入用户名和密码后进入子命令窗口, 然后输入reload进行重载:

    image-20220921172100244

    此时我们刷新浏览器可以看到守护的程序已经启动:

    image-20220921173043546

    我们可以直接在浏览其中直接对其进行重启 停止 以及日志查看等操作

    至此 我们supervisor对frpc的守护已初步实现

supervisor开机自启

我们希望内网机器开机后自动启动supervisor, 在Mac平台实现很简单, 只需执行以下指令即可:

1
brew services start supervisor

brew services内部使用的是launchctl, 简化了launchctl的繁琐操作, brew services的常用命令有:

1
2
3
4
5
6
brew services list  # 查看使用brew安装的服务列表
brew services run formula|--all # 启动服务(仅启动不注册)
brew services start formula|--all # 启动服务,并注册
brew services stop formula|--all # 停止服务,并取消注册
brew services restart formula|--all # 重启服务,并注册
brew services cleanup # 清除已卸载应用的无用的配置

supervisorctl管理工具提供的功能

我们可以利用supervisorctl对我们的守护进行进行启动 停止 重启等操作, 具体的可以输入help进行查看:

image-20220921172300446

  1. 停止 supervisor

    1
    shutdown

    该指令不仅会停止 supervisor, 还会停止其守护的所有程序

  2. 停止某个守护进程

    1
    stop 进程名
  3. 停止所有守护进程

    1
    stop all
  4. 重启某个守护进程

    1
    restart 进程名
  5. 重载整个配置文件

    1
    reload

    所有子进程会重启

  6. 更新配置文件

    1
    update

    配置文件中有改动的进程会被重启 没有改动的不受影响

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

1…91011…48

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

479 日志
139 标签
RSS
© 2024 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%