Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

Linux编程知识集锦

25
Jun
2009

Linux编程知识集锦

By Alex
/ in C,Linux
/ tags Linux编程, 系统编程
0 Comments
库
共享对象

共享库(Shared libraries),在程序执行起始时被自动加载(而非执行过程中随时动态加载)。在链接阶段,必须有共享库才能链接。

soname

使用共享库时,运行时加载的库,应当与链接时期望的库的“版本”一致,即功能上没有不兼容的变化。二进制文件(库、可执行文件)本身知道其依赖的共享库的版本。

如何识别这种变化并没有一致的规范,某些UNIX系统通过soname来版本化共享库,所谓soname就是在共享库名称后附加可选的数字后缀,例如libx.so.1。仅当共享库的接口发生不兼容变化时soname才改变,如果libx从1.0到1.9维持了一致性的接口,它们的soname应该一致。注意soname和文件名不是一回事,1.3版本的libx的文件名可能叫libx.so.1.3,但是它的soname可能是libx.so.1。

通过soname,可以很容易的允许不同版本的共享库存在于同一个系统中(相比Windows的DLL地狱)。不带后缀的example.so常常是指向最新版本的符号链接。不带后缀的so用于链接器,因为gcc不知道去寻找有后缀版本,例如-lpthread只能让gcc去寻找libpthread.so。

soname可以具有主次版本号区分,使用额外的点号分隔。例如: libFOO.so.MAJOR.MINOR.BUGFIX,MAJOR表示ABI不再向后兼容,MINOR表示ABI仍然向后兼容,BUGFIX意味着属于内部改变,ABI不发生变化。

当安装libx库到系统时,可以建立符号链接 libx.so -> libx.so.1 ,当libx出现不兼容升级时,则需要修改前述符号链接,例如 libx.so -> libx.so.2 。这样,新的程序总是和最新的libx版本进行链接。

另一方面,这种符号链接用法可以扩展为,将某个soname链接到特定文件,例如 libx.so.1 -> libx.so.1.3 ,如果1.3版本有一个BUG在1.3.2中修复,可以修改前述符号链接为 libx.so.1 -> libx.so.1.3.2 ,这样基于libx.so.1链接的可执行文件在获得BUG修复的同时,能够找到正确的共享库。

动态加载库

动态加载库(Dynamically loaded libraries),类似于Windows的DLL,运行期间,使用到的时候进行加载。编译链接都不需要目标库文件。

和DLL的区别

DLL中包含两类函数:导出(exported)函数、内部(internal)函数,只有导出函数可以被外部模块使用。SO中的所有函数均可以被外部使用,不需要特殊的export语句导出函数(在大部分UNIX系统中,默认所有符号被导出)。

静态库

静态库(Static libraries),简单的把一些obj文件打包为.a归档文件。优势是执行速度通常更快,而且是静态链接,因此不会出现找不到库、版本错误导致无法执行的情况。缺点是难以维护,库出现BUG则所有依赖都得重现链接

有时,如果依赖库声明的顺序不正确会导致:undefined reference错误。对于静态链接库libA,如果它依赖于链接库libB,那么,在Linker的参数列表中,libA必须在libB前面出现。动态链接库则无此顺序要求。

C语言库
C标准库

属于C编程语言的一部分,它是一个规范(Specification),它定义了若干函数,以及这些函数应该具有什么样的行为。

C标准库的更多内容参考C语言学习笔记。主要头文件包括assert.h ctype.h errno.h float.h limits.h locale.h math.h setjmp.h signal.h stdarg.h stddef.h stdio.h stdlib.h string.h wchar.h time.h

POSIX C库

某种程度上是C标准库的超集 dlopen, fork等函数由POSIX库定义。

GNU C库

GNU的glibc库是C标准库、POSIX C库的超集,在Linux下通常作为共享对象位于/lib/x86_64-linux-gnu/libc.so...

glibc使用C语言的GCC方言编写,是Linux系统的基石。glibc简化了你和系统调用的交互(进行了函数封装)。不使用glibc的情况下,你可以直接写汇编代码来执行系统调用。如果仅仅需要使用C标准库的函数,可以使用musl-libc或者dietlibc等。 

libanl libc libc_nonshared libcrypt libdl libg libmvec libpthread libresolv librt libutil等都来自glibc库。

GNU
简介

操作系统内核仅仅是Linux的一小部分,而大部分应用程序由Linux社区开发。Linux社区支持自由软件的概念,遵从GNU(GNU's not UNIX)通用许可证(GPL)。

GNU项目由Richard Stallman创立,目的是创建一个与UNIX系统兼容,但不受UNIX名字、源代码私有权限制的OS和开发环境。下面列出一些著名的GNU软件:

 软件 说明 
GCC GNU编辑器套件,包括GNU C编译器
G++  C++编译器,GCC的一部分 
GDB 源代码级调试器
GNU make UNIX make的免费版本
Autotools 包含Autoconf、Automake、Libtool、Gettext等组件的自动化构建套件
Bison 与UNIX yacc兼容的语法分析程序生成器
bash 命令解释器
GNU Emacs 文本编辑器

除上表以外,还有大量软件,例如电子表格、源代码管理工具、编译器、因特网工具、桌面环境(GNOME、KDE),都是在GPL条款下发布的。由于GNU软件的重要贡献,现在很多人把Linux称为GNU/Linux。

关于GNU的软件的更多内容:

  1. GCC / GDB:将在本文后续内容中详细介绍
  2. Make可以参考:GNU Make学习笔记
  3. Autotools可以参考:Autotools学习笔记
GCC
简介

GCC是GNU项目的C/C++编译器。

当调用gcc时,正常是执行从预处理、汇编、编译、链接的整个过程,通过某些选项可以在某个步骤中断此处理过程。例如: -c表示不运行链接,只输出汇编器的目标文件。

以 -f、 -W开头的选项(例如-fmove-loop-invariants、-Wformat),通常包含正/反作用的一对选项, -ffoo的反选项是-fno-foo。

C++程序可以使用g++命令来编译。g++是gcc的一个前端应用

环境变量
变量 说明
LIBRARY_PATH 编译期间,GCC基于此环境变量来定位(静态)库所在的目录
LD_LIBRARY_PATH

链接、运行时需要的共享库文件的位置,通常/lib、 /usr/lib默认可用

  1. 链接时:可以通过此环境变量指定目标共享库的位置
  2. 运行时:基于此环境变量寻找共享库

可执行程序或者库所依赖的其它共享库,可以通过 ldd获知

在 /etc/ld.so.conf文件中也可以列出共享库的位置

PKG_CONFIG_PATH pkg-config命令搜索 .pc文件的路径
CPATH C/C++头文件搜索位置
C_INCLUDE_PATH C头文件搜索位置
CPLUS_INCLUDE_PATH C++头文件搜索位置C++头文件搜索位置
安装
Shell
1
2
3
4
5
6
7
8
9
10
11
12
cd ~/CPP/lib/gcc/7.2/src
contrib/download_prerequisites
mkdir build && cd build
../configure -v --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
                --prefix=/home/alex/CPP/lib/gcc/7.2.0
                --enable-languages=c,c++ --disable-multilib
                --program-suffix=-7.1   # 程序名字后缀
 
# 构建
make -j 8
# 安装
make install 
gcc命令
格式
Shell
1
2
3
4
5
6
7
8
# 下面列出最常用的选项,g++基本上接受与gcc同样的选项
gcc [-c|-S|-E] [-std=standard]
    [-g] [-pg] [-Olevel]
    [-Wwarn...] [-Wpedantic]
    [-Idir...] [-Ldir...]
    [-Dmacro[=defn]...] [-Umacro]
    [-foption...] [-mmachine-option...]
    [-o outfile] [@file] infile...
选项
选项 说明
控制输出
-E 仅执行编译预处理
-S 将C代码转换为汇编代码
-c 仅执行编译操作,不进行链接操作
-o 指定生成的输出文件文件
C方言控制
-ansi 在C模式下,等价于  -std=c90。在C++模式下,等价于  -std=c++98
-std

控制使用的方言:

  1. c90 在C模式下等价于-ansi,在C++模式下等价于-std=c++98
  2. c99 ISO C99标准
  3. c11 ISO C11标准
  4. gnu90 ISO C90的GNU方言,包含了一些C99特性
  5. gnu99 gnu11 其它GNU的C方言
  6. c++98 c++03 ISO C++标准,以及2003技术报告
  7. c++11 ISO C++ 2011修正案 
  8. gnu++98 gnu++03 gnu++11 GNU的C++方言
启用/抑制警告
-w  抑制所有警告信息
-Wall 显示所有警告信息
-Wno-unused-parameter 忽略未使用的变量
-Werror  在出现警告的地方强制停止编译
-Werror= 指定哪些警告被看作错误
调试选项
-g

使用操作系统本地格式来生成调试信息,可供GDB使用,可以指定0-3

  1. g0 无任何调试信息
  2. g1 最小化信息,可以支持栈回溯,信息包括函数描述、外部变量描述、行号表,但是没有本地变量信息
  3. g2 默认级别
  4. g3 最大化调试信息,包括宏定义
  5. -ggdb 生成GDB所需的调试信息
  6. -gdwarf 如果支持的话,以DWARF格式产生调试信息
优化选项
-O

指定编译优化级别,0-3,级别越大优化效果越好,编译时间越长

  1. O0 默认,编译时间-,执行时间+
  2. O1 优化代码尺寸-和执行时间- ,内存消耗+,编译耗时+    导致帧指针丢失,无法进行栈backtrace,用 -fno-omit-frame-pointer 解决此问题
  3. O2 进一步优化执行时间--,编译耗时++
  4. O3 进一步优化执行时间---,编译耗时+++
  5. Os 优化代码尺寸--,编译耗时++
  6. Ofast O3外加非精确快速数学运算,执行时间---,编译耗时+++
预处理选项
-D 定义宏,name或者name=definition,默认definition为1
-U 取消宏定义
-I 指定头文件的查找目录, /usr/include自动寻找
-include 用来指定头文件,很少使用
-nostdinc 不去搜索系统的标准C头文件位置
-nostdinc++ 不去搜索系统的标准C++头文件位置
连接选项
-L 指定寻找链接库的目录, /lib /usr/lib /usr/local/lib自动寻找
-l

指定需要链接的库的名称,必须去掉前后缀,并且不留空格,例如 -lm。链接Windows下的库时,只需要去掉后缀

-Wl,-rpath-link

当使用ELF时,一个共享库可能依赖于另外一个。当ld -shared并且将另外一个共享库作为输入的时候就会发生

该选项指定查找依赖的共享库时,优先寻找的一系列目录(冒号分隔)

-Wl,-rpath

修改runtime linker 搜索共享库的路径,优先级高于LD_LIBRARY_PATH

RPATH是写入到ELF文件中的信息:

Shell
1
2
3
4
5
ldd cstudy
    linux-vdso.so.1 =>  (0x00007fff56be3000)
    # 使用非标准的libc
    libc.so.6 => /home/alex/.local/lib/libc.so.6 (0x00007ffa34fee000)
    /home/alex/.local/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007ffa353a3000)
-Wl,--dynamic-linker

使用指定的连接器:

Shell
1
2
3
# 使用自定义的glibc
-Wl,-rpath=/home/alex/.local/lib
-Wl,--dynamic-linker=/home/alex/.local/lib/ld-linux-x86-64.so.2 

-shared 

可以用于编译出共享库
-static 默认情况下,链接时GCC优先使用共享库。该选项强制链接到静态库
-fmessage-length=n 格式化错误信息,最长n字符。对于g++默认72,;其它gcc前端0,即不换行
-Wl,option 向连接器传递选项,多个选项使用逗号分隔
-Wl,-soname=new_soname 设置生成的共享库的soname,等价于-Wl,-hnew_soname
-Wl,--out-implib= 生成的导入库的名称
-Wl,--output-def= 生成的DEF文件的名称
-Wl,--dynamic-linker 指定使用的连接器
-nostartfiles 连接时不使用标准的系统startup文件。标准库文件仍然被使用,除非使用 -nostdlib 或 -nodefaultlibs
-nodefaultlibs 连接时不使用标准系统库
-nostdlib 连接时不使用标准的系统startup文件、库文件
未归类选项
@file 从文件中读取命令行选项
outfile 命令输出文件
infile 命令输入文件的列表
-fpic

作用于编译阶段,告诉编译器产生与位置无关代码(PIC),生成动态库时应该启用该选项,启用该选项后,所有代码通过全局偏移表(GOT)访问地址常量。GOT的条目将由操作系统的动态加载器负责解析。如果GOT超过机器限制的大小,你会得到错误提示,此时可以使用-fPIC 代替

PIC需要机器/OS的支持:例如对于i386来说GCC支持System V,Sun 386i则不支持

不使用PIC的时候,共享库的代码段必须被每个引用进程修改,因而无法实现共享

-fPIC 同上,但是不会受到GOT大小的限制
-fpie -fPIE 类似上面两个选项,但是生成的动态库仅仅供可执行文件链接
-fexec-charset=charset 设置执行字符集,用于字符串和字符常量,默认UTF-8
-fwide-exec-charset=charset 设置宽执行字符集,用于宽字符串和宽字符常量,默认UTF-32或UTF-16,应和wchar_t宽度对应
-finput-charset=charset 输入字符集,当GCC将输入文件(源代码文件)字符集转换为源字符集(GCC内部使用)时使用。默认值UTF-8,可以被该选项或者locale覆盖
 i386/x86-64选项
 -mthreads 用于在MinGW32下支持线程安全的异常处理
 -m32  生成32位代码,long/指针为32位,支持任意i386架构
 -m64   生成64位代码,long/指针为64位,支持x86-64架构
i386/x86-64的Windows选项
-mconsole 用于Cygwin、MinGW目标,提示生成控制台应用程序,连接器会设置PE头的subsystem,该选项是Cygwin、MinGW目标的默认行为
 -mdll 用于Cygwin、MinGW目标,提示生成动态链接库(DLL)
-mnop-fun-dllimport 用于Cygwin、MinGW目标,忽略dllimport属性 
-mthread  用于MinGW目标,使用MinGW专有的线程支持
-mwin32 用于Cygwin、MinGW目标,预处理器将使用Windows典型的预定义宏
-mwindows 

用于Cygwin、MinGW目标,提示生成GUI程序

示例
Shell
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
# 不指定选项,将预处理、汇编链接形成可执行文件,默认输出a.out
gcc hello.c
# 指定输出文件的名称
gcc hello.c -o hello
# 得到预处理输出文件
gcc -E hello.c -o hello.i
# 将预处理输出文件编译为汇编文件hello.s
gcc -S hello.i
# 将汇编文件编译为目标文件hello.o
gcc -c hello.s
# 不指定选项进行链接,生成可执行文件
gcc hello.o -o hello
 
# 多个文件一起编译,形成可执行文件
gcc hellolib.c hello.c -o hello
# 亦可逐个编译,然后链接,这样可以只编译部分文件
gcc -c hellolib.c
gcc -c hello.c
gcc hellolib.o hello.o -o hello
 
# 编译出共享库
gcc -shared hellolib.c -o libhello.so
 
# 强制使用静态hi库来链接
gcc hello.o -static -lhi -o hello
pkg-config命令

显示已经安装的软件包的元数据信息,该工具可以用于:

  1. 检查库的版本号。如果所需要的库的版本不满足要求,它会打印出错误信息,避免链接错误版本的库文件
  2. 获得编译预处理参数,如宏定义,头文件的位置
  3. 获得链接参数,如库及依赖的其它库的位置,文件名及其它一些链接参数
  4. 自动加入所依赖的其它库的设置

要使用pkg-config命令,目标库必须提供一个包含了元数据的.pc文件。对于大部分系统,这些.pc文件存放在:

  1. /usr/lib/pkgconfig
  2. /usr/share/pkgconfig
  3. /usr/local/lib/pkgconfig
  4. /usr/local/share/pkgconfig

但是可以通过环境变量 PKG_CONFIG_PATH进行自定义。

元数据字段
字段 说明
Name 该字段显示一个人类可读的包的名称
Description 该字段显示一个包的简短描述
URL 可以从其下载包的网址
Version 详细的软件包版本字符串
Requires 逗号分隔的,被当前包依赖的包的列表,来自依赖包的标记会被自动合并输出,可以指定操作符:=, <, >, >=, <= 
Requires.private 类似于Requires,但是在进行动态链接时,这些依赖不会被考虑存放在标记中
Conflicts 类似于Requires,列出与当前包冲突的库
Libs 该字段显示当前包依赖的库
Libs.private 显示当前包依赖的私有库,这些库不需要暴露给当前库的使用者,但是当前库需要与之静态链接
Cflags 显示编译标记的列表

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# This is a comment
prefix=/home/hp/unst # this defines a variable
exec_prefix=${prefix} # defining another variable in terms of the first
libdir=${exec_prefix}/lib
includedir=${prefix}/include
 
Name: GObject # human-readable name
Description: Object/type system for GLib # human-readable description
Version: 1.3.1
URL: http://www.gtk.org
Requires: glib-2.0 = 1.3.1
Conflicts: foobar <= 4.5
Libs: -L${libdir} -lgobject-1.3
Libs.private: -lm
Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib/include
环境变量
变量 说明
PKG_CONFIG_PATH 用于搜索.pc文件的目录的集合,默认目录在这些目录后面搜索。默认目录为:libdir/pkgconfig:datadir/pkgconfig,其中libdir、datadir是pkg-config安装时的相关目录
PKG_CONFIG_DEBUG_SPEW 如果该变量被设置,pkg-config将打印所有的调试信息、错误信息
PKG_CONFIG_TOP_BUILD_DIR  设置.pc文件中可能包含的pc_top_builddir变量的值,默认使用$(top_builddir)。只在依赖一个尚未安装的库时用到
PKG_CONFIG_DISABLE_UNINSTALLED 默认的,如果你指定一个foo,并且存在一个库:foo-uninstalled,那么后一个变体被使用,该变量禁止此行为
PKG_CONFIG_ALLOW_SYSTEM_CFLAGS 不从cflags中去除-I/usr/include
PKG_CONFIG_SYSROOT_DIR 修改-I、-L使用的系统根目录,如果该变量设置为/var/target,那么目标库的-I/usr/include/libfoo将被解释为-I/var/target/usr/include/libfoo
PKG_CONFIG_LIBDIR 替换默认的pkg-config搜索目录,一般设置为/usr/lib/pkgconfig
格式
Shell
1
2
3
4
5
6
7
8
pkg-config
 [--modversion] [--help] [--print-errors]
 [--silence-errors] [--cflags] [--libs]
 [--libs-only-L] [--libs-only-l] [--cflags-only-I]
 [--variable=VARIABLENAME] [--define-variable=VARIABLENAME=VARIABLEVALUE]
 [--print-variables] [--uninstalled] [--exists] [--atleast-version=VERSION]
 [--exact-version=VERSION] [--max-version=VERSION] [--list-all] [LIBRARIES...]
 [--print-provides] [--print-requires] [--print-requires-private] [LIBRARIES...]
选项
选项 说明
--modversion 请求显示目标库的版本信息,如果指定多个库并且都成功找到,那么每个库的版本信息占一行
--print-errors 如果一个或者多个目标库、或者其依赖没有被找到,或者解析.pc文件失败,该选项会打印错误信息。环境变量PKG_CONFIG_DEBUG_SPEW覆盖此选项
--cflags 打印在编译目标库时,使用的预处理、编译标记,包括其依赖的所有标记
--cflags-only-I 仅打印--cflags中的 -I部分,即头文件搜索路径
--libs 显示链接标记
--libs-only-L 仅显示-- libs中的 -L/-R部分,即依赖库的搜索路径
--libs-only-l 仅显示-- libs中的 -l部分,即依赖库的名称
--variable=NAME 返回.pc文件中定义的变量
--define-variable=NAME=VALUE 设置某个变量的全局值,覆盖.pc中的值
--print-variables 打印目标库定义的变量的列表
--exists 用于判断目标包/包列表可以被检测到
--atleast-version=VERSION
--exact-version=VERSION
--max-version=VERSION
--msvc-syntax   仅用于Windows,使输出格式中的-l、-L可以被MSVC编译器cl识别
--static 输出适合静态链接的库
--list-all 列出能识别的所有模块
--print-provides 列出目标库提供的所有模块
--print-requires 列出目标库需要的所有模块
--print-requires-private 列出目标库需要静态链接的所有依赖库
示例
Shell
1
2
3
4
# 打印glib-2.0库的prefix变量
# 检查是否存在大于1.3.4版本的glib-2.0库、以及大于1.8.3的libxml库
pkg-config --variable=prefix glib-2.0
pkg-config --exists 'glib-2.0 >= 1.3.4 libxml = 1.8.3' 
ldconfig命令

配置动态连接器的运行时绑定,修改额外的链接库位置后,需要调用该命令使之生效

该命令为参数中提供的路径下找到的共享库,在:

  1. /etc/ld.so.conf(动态库的额外搜索位置)
  2. 受信目录(/lib /usr/lib)

中创建必要的链接、缓存。修改文件/etc/ld.so.conf后应当调用ldconfig。运行时连接器ld.so、ld-linux.so会使用这些缓存

ldconfig通过检查库的文件名和头,来判断那些版本(的库)需要更新其软链接。

格式
Shell
1
2
3
ldconfig [ -nNvXV ] [ -f conf ] [ -C cache ] [ -r root ] directory ...
ldconfig -l [ -v ] library ...
ldconfig -p
选项

-v 打印当前版本号,正在处理的目录、创建的链接
-n 仅仅处理命令行指定的目录。不处理/etc/ld.so.conf中列出的目录,以及受信目录(/lib、/usr/lib)
-N 不重建缓存
-X 不更新软链接
-f 使用其它文件而非/etc/ld.so.conf
-C 使用其它缓存而非/etc/ld.so.cache 

ldd

打印指定的程序、共享库,所依赖的共享库列表

格式
Shell
1
ldd [OPTION]... FILE...
选项

-u 打印没有使用的直接依赖
-d 进行重定位,报告所有缺失的对象
-r 同时对数据对象和函数进行重定位,报告缺失的对象或函数

objdump

显示一个或多个目标文件(object files)的信息

格式
Shell
1
objdump [options] objfile...
选项

-a 如果某个目标文件是归档文件(静态库),显示归档的头信息
-f 显示目标文件一般性的头
-p 显示特定目标文件格式的私有头
-h 显示目标文件section header的基本信息
-x 显示所有可用的头信息,包括符号表、relocation entries
-d 执行反汇编,仅针对可能存在代码的section
-D 对所有section执行反汇编
-S 显示混合着汇编的源码,隐含-d
-g 显示Debug信息
-e 显示Debug信息,输出格式与ctags工具兼容
-C  解码(demangle)低级的符号名称为用户可读名称,让C++函数名可读

示例
Shell
1
2
3
4
5
# 显示共享库的soname
objdump -p /usr/lib/mysql/plugin/auth.so | grep SONAME
 
# 查找符号
objdump -x /home/alex/.local/lib/libc.so.6 | grep getaddrinfo
readelf 

显示ELF文件的信息。ELF是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的,也是Linux的主要可执行文件格式。

选项
选项 说明
-a 等价于 -h -l -S -s -r -d -V -A -I
-h

显示ELF头信息,这是一些最基础的信息:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# readelf -h /home/alex/.local/lib/libc.so.6
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  # 可以看到操作系统信息
  OS/ABI:                            UNIX - GNU
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  # 可以看到体系结构信息
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  # 程序入口点地址
  Entry point address:               0x21d10
  Start of program headers:          64 (bytes into file)
  Start of section headers:          12004816 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         10
  Size of section headers:           64 (bytes)
  Number of section headers:         82
  Section header string table index: 79 
-l 
--program-headers
--segments
显示program headers信息
-S
--section-headers
--sections
显示section headers信息
-g
--section-groups
显示section groups信息
-t
--section-details

显示section详细信息

Shell
1
2
3
4
# 查看调试文件信息
readelf -S /lib/x86_64-linux-gnu/libc-2.23.so | grep -E 'debug|.build'
  [ 1] .note.gnu.build-i NOTE             0000000000000270  00000270
  [70] .gnu_debuglink    PROGBITS         0000000000000000  001c6dd1
-s   --syms
--symbols
显示符号表
--dyn-syms 显示动态符号表
objcopy

可以将可执行文件中的调试信息抽取出来:

Shell
1
2
objcopy --only-keep-debug foo foo.debug
strip -g foo 
GDB
简介

GNU的调试器,可以:

  1. 启动应用程序,并指定任何参数
  2. 在特定情况下,让应用程序暂停执行
  3. 暂停指向后,检查应用程序的状态
  4. 改变应用程序状态
源码路径映射

编译器有时候不去记录源文件所被编译时的实际目录,而仅仅记录源文件的名称或相对路径。即使记录了,在你调试的时候,编译目录可能已经删除,这就让gdb面临如何将符号表中的源文件路径映射到Debugger机器的源文件的问题

gdb使用一系列目录,从中搜索源文件,此所谓source path。每次gdb需要源文件时,逐个搜索这些目录,直到找到一个名称匹配的文件

例如,假设可执行文件符号表中引用/usr/src/foo-1.0/lib/foo.c,而Debugger的source path设定为 /mnt/cross,则文件的(在Debugger上)搜索顺序为:

  1. 按符号表字面值搜索:/usr/src/foo-1.0/lib/foo.c
  2. 添加source path作为前缀:/mnt/cross/usr/src/foo-1.0/lib/foo.c
  3. 添加source path作为前缀,剥离符号表的目录部分:/mnt/cross/foo.c

符号表引用的相对路径../lib/foo.c类似,分别搜索 ../lib/foo.c、/mnt/cross/../lib/foo.c、/mnt/cross/foo.c

符号表

在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。

目标文件中通常会有一个包含了所有外部可见标识符的符号表。在链接不同的目标文件时,链接器会使用这些文件中的符号表来解析所有未解析的符号引用。

在调试阶段,需要通过符合表来解读各种信息。

对于Ubuntu来说,软件包的符号表独立分发。它们发布在 ddebs.ubuntu.com,命名为 <package>-dbgsym。相比传统的 <package>-dgb,它的优势包括:

  1. dbgsym可以自动生成、自动上传到ddebs.ubuntu.com。维护成本低
查找调试信息

GDB支持在独立文件中存放的符号信息,并且能够自动发现、加载。

GDB支持两种方式来指定调试信息文件:

  1. 在可执行文件中,包含debug link,指定了独立调试文件的名称,debug link是可执行文件中名为.gnu_debuglink的特殊section。调试文件名称格式通常是executable-name.debug。debug link 还提供调试文件的CRC检查,用于验证调试文件、可执行文件来自同一次build
  2. 在可执行文件中,包含build ID,在对应的调试文件中,此ID也存在。build ID是可执行文件中(通常)名为 .note.gnu.build-id的section

对于方式debug link方式,GDB依次在以下位置寻找调试文件:

  1. 在可执行文件所在目录
  2. 名为.debug的子目录
  3. 全局debug目录

对于build ID方式,GDB会在全局debug目录的.build-id子目录中查找nn/nnnnnnnn.debug文件。前面的nn是build ID的最前面2个HEX字符,nnnnnnnn则是build ID的剩余部分。

举例来说,调试/usr/bin/ls的时候,如果debug link指定了 ls.debug,并且具有build ID是abcdef1234,并且全局debug目录包括/usr/lib/debug,那么以下位置用于寻找调试文件:

  1. /usr/lib/debug/.build-id/ab/cdef1234.debug
  2. /usr/bin/ls.debug
  3. /usr/bin/.debug/ls.debug
  4. /usr/lib/debug/usr/bin/ls.debug

全局debug目录取决于GDB的配置项  --with-separate-debug-dir。

你也可以使用交互式命令查看、设置全局dbug目录:

Shell
1
2
3
4
# 查看
(gdb) show debug-file-directory
# 设置,path separator分隔
(gdb) set debug-file-directory directories  

找到调试文件,是设置断点的前提条件。运行到断点之后,你可能遇到:res_send.c: No such file or directory. 这样的错误,这是因为没有找到源码。可以用下面的命令来设置源码路径:

Shell
1
2
3
(gdb) directory /glibc-2.27/resolv
# 指定多个源码路径
(gdb) directory /glibc:$cdir:$cwd
gdb命令
格式
Shell
1
2
3
4
5
6
7
8
9
gdb [-help] [-nh] [-nx] [-q] [-batch] [-cd=dir] [-f] [-b bps]
    [-tty=dev] [-s symfile] [-e prog] [-se prog] [-c core] [-p procID]
    [-x cmds] [-d dir] [prog|prog procID|prog core]
 
# 连接到指定的进程以调试
gdb -p 1234
 
# 调试应用程序
gdb /path/to/program
选项
选项 说明
-s  从指定的文件中读取符号表
-e 指定可执行文件,在适合(发起run命令)的时候执行。也用于和core dump联用来检查pure data
-se 从文件读取符号表同时将其作为可执行文件
-c 指定需要检查的core dump
-x 从文件中读取GDB命令

-ex

执行指定的GDB命令
-d 指定搜索源码文件的目录
-cd 指定工作目录
-write 启用写入到可执行文件/core文件
-tty 使用指定的设备作为程序的stdio

-p

Attach到指定的进程
-tui 使用CLUI模式启动,在容器中运行的GDB,CLUI模式下显示有问题,建议从宿主机上进入容器的空间执行GDB
子命令
子命令 说明
常用
target

连接到目标,例如gdbserver或者进程:

Shell
1
2
# 连接到gdbserver
target remote localhost:2345
attach 附着到进程进行调试
detach 解除对进程的调试
<ENTER> 再次执行上一个命令
ctrl + c 立即暂停正在运行中的程序,暂停在当前正在执行的行,等待后续调试指令
break

设置断点,注意,你可能需要设置在期望函数的上一行,否则它可能是在调用函数结束之后才暂停,而不是调用开始前

示例:

Shell
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
# 在主函数执行前暂停
b main
 
# 在main.cpp的55行设置断点
break main.cpp:55
 
# 在指定的内存地址上设置断点
b *0x08048375
 
# 在main函数的第7个指令上设置断点
b *main+7
 
 
# 指定源文件绝对路径
break  /tmp/KeyDB/src/replication.cpp:2302
 
# 函数断点,在执行函数前暂停程序
break funcname
 
# 在当前文件的第N行设置断点
b N
# 在当前行后面的第N行设置断点
b +N
 
# 条件断点
break line-number if condition
break main.cpp:55 if x > 1
 
# 线程断点
# 使用info threads获得线程编号
break line thread thread-number
 
 
# 列出所有断点
info break
 
# 根据序号删除断点
del breakpointnumber
 
# 禁用/启用断点
dis breakpointnumber
en  breakpointnumber
 
# 忽略断点,知道经过它X次
ignore breakpointnumber x
 
# 清除(当前暂停在的)断点
clear
tbreak 一次性断点
c

continue,让程序从暂停中恢复,当遇到下一个breakpoint或者ctrl + c程序会再次暂停

c N 继续且忽视当前断点N次

until until line-number,一直执行,直到指定的行数
s

step,执行一行代码,如果正在执行的是函数,则进入此函数。相当于大部分调试工具的step into

s N 则执行N行代码

si step的精确版,每次仅执行一个汇编指令,可能需要多次才能执行完一行
n next,执行到下一行,相当于大部分调试工具的step over
ni next的精确版,每次仅执行一个汇编指令,可能需要多次才能执行完一行
f 运行直到当前函数结束,相当于大部分调试工具的step out
l list,显示当前行附近的代码。默认显示20行
bt backtrace,打印栈追踪
u 转到栈帧的上一级别
d 转到栈帧的下一级别
where 打印所有栈帧的信息,包括指令地址、函数名、源码位置。如果在调试时源码路径映射不对(导致显示反编译代码),可以通过此命令知晓构建时的源码路径
directory

添加指定的目录到源码搜索路径中 

示例:

Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 解决CLion中,由于源码路径不正确导致显示汇编的问题
where
# #30 0x00000000010ad0b0 in event_persist_closure (ev=<optimized out>, base=0x32fc000) at ../event.c:1580
# #31 event_process_active_single_queue (base=base@entry=0x32fc000, activeq=0x32260b0, max_to_process=max_to_process@entry=2147483647, endtime=endtime@entry=0x0) at ../event.c:1639
# 这里的libevent的源码映射失败,构建时,源码位于工作目录的上级目录
# #32 0x00000000010ada67 in event_process_active (base=0x32fc000) at ../event.c:1738
# #33 event_base_loop (base=0x32fc000, flags=0) at ../event.c:1961
# #34 0x0000000000a5067c in Envoy::Event::DispatcherImpl::run (this=0x322b3f0, type=Envoy::Event::Dispatcher::RunType::Block) at source/common/event/dispatcher_impl.cc:165
# #35 0x00000000009d74c5 in Envoy::Server::InstanceImpl::run (this=0x32bb200) at source/server/server.cc:466
# #36 0x0000000000425baa in Envoy::MainCommonBase::run (this=0x32ba8b0) at source/exe/main_common.cc:103
# #37 0x000000000040cd68 in Envoy::MainCommon::run (this=0x32ba480) at bazel-out/k8-dbg/bin/source/exe/_virtual_includes/envoy_main_common_lib/exe/main_common.h:86
# #38 0x000000000040a4bf in main (argc=19, argv=0x7ffe63314218) at source/exe/main.cc:37
# #39 0x00007f7407aaff45 in __libc_start_main (main=0x40a44e <main(int, char**)>, argc=19, argv=0x7ffe63314218, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffe63314208) at libc-start.c:287
# #40 0x0000000000409e45 in _start ()
# libevent的源码目录是:/home/alex/CPP/lib/libevent/2.1.8-stable
# 而当前的工作目录是:/home/alex/CPP/projects/clion/envoy
# 将libevent源码添加到源码搜索路径
directory /home/alex/CPP/lib/libevent/2.1.8-stable

i

info

显示各种信息:

info all-registers 列出所有寄存器及其值
info args 显示当前栈帧的参数值
info locals 显示当前栈帧的本地变量值
info variables 显示全局和静态变量的值
info break  显示断点列表

示例:

Shell
1
2
# 得到eax寄存器的值,通常是函数调用返回值
i r eax 
set

可以用来设置变量值:

Shell
1
2
3
4
set varname=varvalue
 
# 设置寄存器rdx的值
set $rdx = 10000
p print,可以用来打印变量值: print varname
help 使用 help commandname可以查看某个命令的帮助
q quit,退出GDB
断点
watch 为表达式设置监控点,例如 watch x > 1表示当x大于1时暂停执行
break-range 为地址范围设置断点
catch 设置捕获点以捕获事件
commands 指定到达断点时执行的命令
delete 删除断点或自动显示表达式
skip 单步跟踪时,忽略指定的函数、文件
tbreak 设置临时断点
disable 禁用断点
enable 启用断点
程序操控
advance

运行到指定位置:

Shell
1
2
# 向前跑30行
advance +30
finish 执行直到选中的栈帧返回
handle 指定如何处理信号
jump 跳转正在执行的程序到指定的行或地址
kill 杀死被调试的程序
queue-signal   入队一个信号,在当前线程resume后传递给它
run 启动被调试的程序
start 启动被停止在主程序开始处的程序
thread 切换到其它线程
thread find  使用正则式查找线程
thread name  设置当前线程的名称
文件和路径
set directories 设置源码搜索路径
set substitute-path set substitute-path from to 设置查找源码使用的路径映射
set sysroot

设置包含了调试目标的库的副本的目录,当连接到gdbserver进行远程调试时有用

Shell
1
2
3
4
5
6
7
8
# 从指定的本地目录来寻找库
set sysroot [Directory]
 
# 从指定的远程目录寻找库,一般设置为 /
set sysroot remote:/
set sysroot remote:[Remote directory]
set solib-absolute-prefix [Directory]
show sysroot

如果运行gdbserver的远程服务器、运行gdb的本地服务器,上面的库不匹配,设置此变量可以解决找不到调试文件的问题 

add-symbol-file   从文件加载符号表
cd  设置调试器的工作目录
core-file  使用指定的文件作为coredump,用于检查内存和寄存器
generate-core-file  生成正在调试的程序的coredump
load 动态的加载文件到当前程序中
nosharedlibrary  卸载所有共享库的符号
状态和信息
info address

描述指定的符号在何处存储

info symbol 显示指定地址的符号
info all-registers  列出所有寄存器及其值
info args 显示当前栈帧的参数值
info locals 显示当前栈帧的本地变量值
info variables 显示全局和静态变量的值
info breakpoints 显示断点状态
info files 目标的名称以及被调试的文件
info frame 选中栈帧的信息
info functions 列出所有函数名
info macro 显示宏定义
info macros 显示指定 LINESPEC的所有宏定义
info program 显示程序的执行状态
info stack 显示栈的追踪
info target 显示正在调试的目标名
info threads 显示线程列表
info source 显示当前(被中断时断点所在文件)源文件的信息
info sources  

显示源文件列表。启动gdb时,其source path仅仅包含:

$cdir 编译目录
$cwd 当前工作目录

info set 显示所有GDB设置
info proc cwd 显示当前被调试程序的工作目录
info proc exe   显示当前被调试程序的可执行文件的路径
show architecture  显示目标体系结构
show args 显示被调试目标启动时的参数
show version

显示GDB版本信息

show sysroot 显示当前的system root
show substitute-path 显示当前的源码映射
反汇编
disassemble 反汇编,打印所有指令: disassemble main
x/8i 反汇编,打印前8个指令: x/8i main
gdbserver命令

这是一个控制程序,允许你通过Remote GDB连接到应用程序。要在目标系统中运行gdbserver,你需要应用程序的二进制文件,但是不一定需要符号表。

格式
Shell
1
2
3
gdbserver [OPTIONS] COMM PROG [ARGS ...]
gdbserver [OPTIONS] --attach COMM PID
gdbserver [OPTIONS] --multi COMM

其中:

  1. COMM:要么是一个TTY设备(串口调试),要么是HOST:PORT(用于监听GDB客户端连接),还可以指定 - 或者 stdio,表示使用gdbserver的标准输入/输出
  2. PROG  ARGS:被调试的可执行文件的路径和参数
  3. PID:需要附着到的进程的PID
选项
选项 说明
--attach 附着到指定进程
--multi 不指定需要执行/Attach的应用程序,仅仅在显示给出命令时才退出
--once 第一个连接关闭后,关闭服务器
--wrapper WRAPPER -- 通过运行WRAPPER来启动应用程序
--disable-randomization
--no-disable-randomization
运行应用程序是,是否禁用地址空间随机化
--startup-with-shell
--no-startup-with-shell
是否在Shell中运行应用程序
--debug 启用一般性的调试输出
示例
Shell
1
2
# 附着到正在运行的程序,在2345端口上等待客户端连接
gdbserver --attach 0.0.0.0:2345 449
GDB前端
tui

这是GDB自带的前端,使用 gdb -tui即可启动。

命令 说明
ctrl-x o 窗口之间切换焦点
layout

设置窗口布局:

src 上面源码、下面命令
split 上面源码、中间汇编、下面命令

ctrl-l 重新绘制窗口
Emacs GDB

Emacs编辑器提供的功能。按 M-x,输入gdb即可使用。

输入命令 gdb-many-windows可以进入多窗口模式。默认6个窗口,分别是GDB终端、本地变量列表、源码、栈帧、断点。

输入命令 gdb-display-disassembly-buffer可以将某个窗口切换为反汇编。

命令 功能 默认快捷键 熟悉快捷键
添加断点 gud-break C-x C-a C-b  
删除断点 gud-remove C-x C-a C-d  
继续运行程序 gud-go    
单步执行,不进入函数 gud-next C-x C-a C-n F6
单步执行,进入函数

gud-step

C-x C-a C-s F5
跳出当前函数 gud-finish C-x C-a C-f F7
运行到光标所在语句 gud-until C-x C-a C-u Ctrl+F8
继续运行程序 gud-cont C-x  C-a  C-r F8
cgdb

如果不习惯Emacs的快捷键,可以使用cgdb,它更加简洁,vim用户友好。

默认情况下,cgdb将控制台分为两个窗口:

  1. 源码窗口:使用vim风格命令,输入:quit可以退出
  2. GDB窗口:使用GDB命令,输入quit或者C-d退出

可用窗口列表:

窗口 说明
源码窗口

在这个窗口查看源码,使用快捷键进行单步跟踪。支持C, C++语法高亮,支持源码导航导航到指定的源码,然后按Space即可设置断点。支持在源码中基于正则式进行搜索

快捷键:

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
  ESC            进入命令模式
  i              进入GDB模式(输入焦点变为GDB窗口)
  s              进入GDB模式,支持滚动
  C-t            为被调试程序打开新的TTY窗口
  C-w            切换水平/垂直布局
  num            k或者num up-arrow 光标向上移动num行
  num j          或者 num down-arrow 光标向下移动num行
  h              或者left-arrow 向左移动光标
  l              或者 right-arrow 向右移动光标
  C-b            或者 page up 向上翻页
  C-u            向上翻半页
  C-f            或者 page down 向下翻页
  C-d            向下翻半页
  m[a-zA-Z]      设置标记
  '[a-zA-Z]      跳转到标记
  ''             跳转到上一个位置
  '.             跳转到正在执行的行
  /              从当前光标处开始搜索
  ?              从当前光标处反向搜索
  n              前向下一个匹配
  N              反向下一个匹配
  o              打开文件对话框
  spacebar       在当前行设置断点
  t              在当前行设置一次性断点
  F5             发送run命令给GDB
  F6             发送continue命令给GDB
  F7             发送finish命令给GDB
  F8             发送next命令给GDB
  F10            发送step命令给GDB

 

命令: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
:set asr           自动加载修改后的源文件
 
:set color         是否在可能的时候用彩色显示,默认no
:set dwc           是否在调试窗口中使用彩色显示
 
:set dis           是否显示汇编,默认off,如果on,则显示混合的汇编、源码
 
:set ic            搜索时是否忽略大小写,默认off
:set hls           是否高亮显示所有匹配
 
:set showmarks     源码窗口中是否默认显示标记
 
:set ts=number     TAB显示为空格的数量,默认8
 
:set wso=style     窗口布局方向 horizontal  或者 vertical
:set hls
 
:map lhs rhs       新建或者覆盖原有的按键映射

命令可以写在 ~/.cgdb/cgdbrc文件中,不需要前导的冒号。

map命令用于修改按键映射,例如: 

map <F2> ip<Space>argc<CR>表示,按F2后,会自动输入i进入GDB模式,然后输入 p空格argc回车
map <F9> :until<CR>表示,按F9后执行:unti回车

下面的配置修改成Eclipse风格的调试快捷键:

1
2
3
4
5
6
7
8
9
10
11
12
13
set ignorecase
set ts=4
set wso=vertical
set dis=on
 
map <F3>     isi<CR><ESC>
map <F4>     ini<CR><ESC>
map <F5>     :step<CR>
map <F6>     :next<CR>
map <F7>     :finish<CR>
map <F8>     :continue<CR>
map <F9>     :run<CR>
map <F10>    :until<CR>

其中尖括号中的是keycode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<Esc>        escape key
<Up>         cursor up key
<Down>       cursor down key
<Left>       cursor left key
<Right>      cursor right key
<Home>       home key
<End>        end key
<PageUp>     page up key
<PageDown>   page down key
<Del>        delete key
<Insert>     insert key
<Nul>        zero
<Bs>         backspace key
<Tab>        tab key
<NL>         linefeed
<FF>         formfeed
<CR>         carriage return
<Space>      space
<Lt>         less-than
<Bslash>     backslash
<Bar>        vertical bar
<F1>         function keys 1 to 12
<C-...>      control keys
<S-...>      shift keys
GDB窗口 在这个窗口中可以使用GDB命令
文件对话框

用于选择需要查看的文件

在源码窗口输入 o即可打开对话框,输入 q退出对话框

状态栏

最右侧是 *表示当前焦点在GDB窗口,按 ESC即可让源码窗口获得焦点(进入CGDB模式),输入 i焦点回到GDB窗口(进入GDB模式)

常用代码片段
向程序传递参数

Linux或者UNIX下C语言程序入口函数的标准声明方式如下:

C
1
2
3
4
int main(int argc, char *argv[]);
//argc 表示参数的个数,其中第一个参数是程序的名称
//argv 表示参数的数组
//多个参数使用空白符区分,如果单个参数中间有空白符,使用引号包围该参数

下面的代码说明如何读取程序参数:

C
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
    int arg;
    for ( arg = 0; arg < argc; arg++ )
    {
        if ( argv[arg][0] == '-' ) printf( "选项: %s\n", argv[arg] + 1 );
        else printf( "参数: %s\n", argv[arg] );
    }
    exit( 0 );
}

X/Open规范定义了命令行选项的标准格式,并提供了获取命令行选项的接口:getopt函数:

C
1
2
3
4
5
6
7
8
9
#include <unistd.h>
int getopt(int argc, char *const argv[], const char *optstring);
//optstring:字符列表,表示支持的选项,每个字符代表一个选项,:表示前面的选项需要一个值
//例如if:lr表示支持4个选项,其中f后面紧跟一个关联值
extern char *optarg;
//optind存放下一个待处理参数的索引
//如果遇到无法识别的选项,getopt返回一个?并将此选项保存到optopt中
//如果一个选项要求关联值,但是没有传递,则getopt返回一个?
extern int optind, opterr, optopt;

反复调用getopt,即可依次得到选项,示例如下:

C
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
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
    int opt;
    //直到返回值是-1,依次读取
    while ( ( opt = getopt( argc, argv, ":if:lr" ) ) != -1 )
    {
        switch ( opt )
        {
            case 'i' :
            case 'l' :
            case 'r' :
                printf( "option: %c\n", opt );
                break;
            case 'f' :
                printf( "filename: %s\n", optarg ); //读取参数值
                break;
            case ':' :
                printf( "option needs a value\n" );
                break;
            case '?' :
                printf( "unknown option: %c\n", optopt ); //读取未知参数名
                break;
        }
    }
    for ( ; optind < argc; optind++ )
        printf( "argument: %s\n", argv[optind] );
    exit( 0 );
}

GNU C函数库支持双横线开头的长参数,函数为getopt_long,有关联值的参数使用 --option=value 的形式给出。该函数用法示例如下:

C
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
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define _GNU_SOURCE
#include <getopt.h>
int main( int argc, char *argv[] )
{
    int opt;
    struct option longopts[] = {
            //此结构的定义如下:
            //name:长选项的名字,缩写亦可,只要不与其它选项混淆
            //has_arg:是否带参数,0表示不带;1表示必须有一个参数;2表示有一个可选参数
            //flag:设置为NULL,那么找到该选项时,getopt_long返回val中给定的值(对应的短选项)
            //      否则,getopt_long返回0并且将val的值写入flag指向的变量
            //val:getopt_long为该选项返回的值
            { "initialize", 0, NULL, 'i' },
            { "file", 1, NULL, 'f' },
            { "list", 0, NULL, 'l' },
            { "restart", 0, NULL, 'r' },
            { 0, 0, 0, 0 }
    };
    while ( ( opt = getopt_long( argc, argv, ":if:lr", longopts, NULL ) ) != -1 )
    {
        switch ( opt )
        {
            case 'i' :
                case 'l' :
                case 'r' :
                printf( "option: %c\n", opt );
                break;
            case 'f' :
                printf( "filename: %s\n", optarg );
                break;
            case ':' :
                printf( "option needs a value\n" );
                break;
            case '?' :
                printf( "unknown option: %c\n", optopt );
                break;
        }
    }
    for ( ; optind < argc; optind++ )
        printf( "argument: %s\n", argv[optind] );
    exit( 0 );
}
获取环境变量
C
1
2
3
4
5
#include <stdlib.h>
//读取环境变量
char *getenv(const char *name);
//设置环境变量,以name=value形式的字符串作为参数
int putenv(const char *string);

除了上面两个方法外,可以通过environ变量直接访问环境变量:

C
1
2
3
#include <stdlib.h>
//字符串数组
extern char **environ;

示例如下:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <stdio.h>
extern char **environ;
int main()
{
    char **env = environ;
    while ( *env )
    {
        printf( "%s\n", *env );
        env++;
    }
    exit( 0 );
}
日期与时间处理

 UNIX/Linux下的时间的表示,均是以格林尼治(GMT)时间1970-01-01 00:00:00为起点进行计时的,表示为从这一时间点流逝的时间。时间使用预定义类型: time_t 表示,通过以下函数可以得到当前时间值(流逝的秒数):

1
2
3
4
5
#include <time.h>
//如果tloc不为NULL,那么返回值同时写入到该指针
time_t time( time_t *tloc );
//把结构还原为时间值
time_t mktime(struct tm *timeptr);

 difftime函数可以返回两个时间点之间的差值:

C
1
2
3
#include <time.h>
//返回time1 - time2
double difftime(time_t time1, time_t time2);

使用gmtime函数可以把time_t分解为一个结构:

C
1
2
3
4
5
6
7
8
9
10
11
12
#include <time.h>
struct tm *gmtime( const time_t timeval );
//结构成员包括:
//int tm_sec,秒0-61
//int tm_min,分0-59
//int tm_hour,时0-23
//int tm_mday,月份中的日期1-31
//int tm_mon,月份0-11
//int tm_year,年度,从1900开始算
//int tm_wday,星期几,0-6,周日为0
//int tm_yday,年份中的日期,0-365
//int tm_isdst,是否夏令时

以上函数处理的都是GMT标准时间,如果需要得到当地时间,可以使用:

C
1
2
#include <time.h>
struct tm *localtime(const time_t *timeval);

以人类易于阅读的方式输出日期,可以使用:

C
1
2
3
4
5
#include <time.h>
char *asctime( const struct tm *timeptr );
//输出格式为固定26字符:Sun Jun 9 12:34:56 2007\n\0
//下面这个函数等价于:asctime(localtime(timeval))
char *ctime( const time_t *timeval );

Linux提供了strftime函数,可以对日期格式进行细致的格式化:

C
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
#include <time.h>
size_t strftime( char *s, size_t maxsize, const char *format, struct tm *timeptr );
/**
* 其中,format为格式化字符串,类似于printf:
* %a 星期几的缩写
* %A 星期几的全称
* %b 月份的缩写
* %B 月份的全称
* %c 日期和时间
* %d 月份中的日期01-31
* %H 小时0-23
* %I 12小时制中的小时01-12
* %j 年份中的日期,001-366
* %m 年份中的月份,01-12
* %M 分钟,00-59
* %p a.m表示上午,p.m.下午
* %S 秒,00-61
* %u 星期几,1-7,周一为1
* %U 一年中的第几周,01-53(周日算第一天)
* %V 一年中的第几周,01-53(周一算第一天)
* %w 星期几,0-6,周日为9
* %x 本地格式的日期
* %X 本地格式的时间
* %y 年份-1900
* %Y 年份
* %Z 时区名
* %% 字符%
*/
使用临时文件
C
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
//创建一个唯一的文件名,缓冲区至少L_tmpnam长
char *tmpnam( char *s );
//创建一个唯一的临时文件,以读写方式打开
FILE *tmpfile( void );
 
//来自UNIX的临时文件函数,字符串模板必须以XXXXXX结尾,这6个字符会被替换
#include <stdlib.h>
char *mktemp( char *template );
int mkstemp( char *template );
获取用户信息

除了init之外,所有Linux进程均是由其它程序或者用户启动的。每个程序都是以某个用户的名义去运行的,用户的内部识别方式是UID。

一般情况下,启动程序的用户的UID,即是程序的有效UID;如果程序的SUID位被设置,那么程序的所有者将作为其有效UID,不论谁启动该程序。

UID的类型是 uid_t ,定义在头文件 sys/types.h 中,是一个小整数,某些UID是系统预定义的,其它则是系统管理员添加的,一般情况下UID大于100。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sys/types.h>
#include <unistd.h>
//得到程序关联的UID,通常是启动程序的用户的ID
uid_t getuid( void );
//返回当前用户关联的登录名
char *getlogin( void );
 
#include <sys/types.h>
#include <pwd.h>
//获取用户信息的标准接口,出错时返回空指针并设置errno
struct passwd *getpwuid( uid_t uid );
struct passwd *getpwnam( const char *name );
/**
* passwd结构成员:
* char *pw_name 用户登录名
* uid_t pw_uid UID号
* gid_t pw_gid GID号
* char *pw_dir 家目录
* char *pw_gecos 用户全名
* char *pw_shell 默认Shell
*/
获取主机信息

uname命令可以获取主机的若干信息,例如主机名等。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <unistd.h>
//获取主机名,字符串被假设最少有namelen长,成功返回0否则返回-1
int gethostname( char *name, size_t namelen );
 
#include <sys/utsname.h>
//使用uname系统调用,可以获取更多的信息
int uname( struct utsname *name );
struct utsname
{
    char sysname[]; //操作系统名称
    char nodename[]; //主机名称
    char release[]; //操作系统的发布级别
    char version[]; //操作系统的版本号
    char machine[]; //硬件类型
};
//得到唯一的机器ID
#include <unistd.h>
long gethostid( void );
使用系统日志功能

Linux系统文件一般被存放到专用的目录,例如/usr/adm或者/var/log目录。UNIX规范通过syslog函数为所有程序产生日志信息提供了统一接口:

C
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
#include <syslog.h>
//该函数向系统的日志设施(facility)发送一条日志消息
//priority:严重级别与实施值的位或
//LOG_EMERG   紧急情况,该信息可能广播给所有用户
//LOG_ALERT   高优先级故障,例如数据库崩溃,该信息可能EMAIL管理员
//LOG_CRIT    严重错误,例如硬件故障
//LOG_ERR     错误
//LOG_WARNING 警告
//LOG_NOTICE  需要注意的特殊情况
//LOG_INFO    一般信息
//LOG_DEBUG   调试信息,该信息可能被忽略,其它级别的一般均记录日志
//
//message:基于printf风格的消息模板,%m表示当前errno
//arguments:模板参数
void syslog( int priority, const char *message, arguments...);
 
//其它函数
//关闭openlog打开的文件描述符
void closelog( void );
//该函数用于改变日志的表示方式,该函数会打开一个文件描述符,通过它写日志,使用syslog前并不是必须调用该函数
//ident:添加在日志信息前面的前缀
 
//logopt:对后续syslog调用的行为进行配置,是以下四个值的位或:
//LOG_PID    在日志信息中包含进程标识符
//LOG_CONS   如果无法记录到文件,信息被发送到控制台
//LOG_ODELAY 第一次调用syslog时才打开日志设施
//LOG_NDELAY 立即打开日志设施
 
//facility:后续syslog调用的默认设施值
void openlog( const char *ident, int logopt, int facility );
//设置日志掩码,来控制日志的优先级,没有在此掩码中置位的优先级,在后续syslog调用中,全部被丢弃
int setlogmask( int maskpri );

syslog创建的日志包含消息头和消息体,消息体由设施值和时间导出,消息体由调用者提供。syslog使用示例如下:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <syslog.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    int logmask;
    openlog( "logmask", LOG_PID | LOG_CONS, LOG_USER );
    syslog( LOG_INFO, "informative message, pid = %d", getpid() );
    syslog( LOG_DEBUG, "debug message, should appear" );
    logmask = setlogmask( LOG_UPTO( LOG_NOTICE ) ); //设置日志掩码,改变优先级
    syslog( LOG_DEBUG, "debug message, should not appear" );
    exit( 0 );
}
资源和限制

Linux系统上运行的程序会受到资源限制的影响,这些限制包括硬件方面的物理性限制、系统策略的限制、具体实现的限制(例如整数的长度、文件名允许的最大字符数)。

头文件limits.h包含了一些操作系统方面限制的显式常量:

 常量 说明 
NAME_MAX 文件名最大字符数
CHAR_BIT char类型值的最大位数
CHAR_MAX char类型的最大值
INT_MAX int类型的最大值

头文件sys/resource.h提供了资源操作方面的定义:

C
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
#include <sys/resource.h>
#include <sys/time.h>
/**
* 获取优先级。默认的优先级是0,正数用于后台任务,负数使一个程序运行的更加频繁,获得更多CPU时间
* 优先级的有效范围:-20 ~ +20
* @param which 指明who参数的含义:PRIO_PROCESS 进程标识符;PRIO_PGRP 进程组;PRIO_USER 用户
* @param who 标识符
* @return 成功时返回优先级,失败返回-1并设置errno,因为-1本身是有效的优先级,所有调用函数前应当置errno为0
*/
int getpriority( int which, id_t who );
//设置优先级。普通用户只能降低优先级,不能提高
int setpriority( int which, id_t who, int priority );
/**
* 获取资源限制
* @param resource 资源类型,RLIMIT_开头的若干常量
*/
int getrlimit( int resource, struct rlimit *r_limit );
struct rlimit
{
    rlim_t rlim_cur;   //当前软限制,建议性的最好不要超过的限制,如果超过可能导致库函数返回错误
    rlim_t rlim_max;   //硬限制,如果被超越,系统可能发送信号终止程序
};
int setrlimit( int resource, const struct rlimit *r_limit );
/**
* 将CPU时间信息写入到r_usage中
* @param who RUSAGE_SELF 仅返回当前程序使用的信息;RUSAGE_CHILDREN 包括子进程的使用信息
*/
int getrusage( int who, struct rusage *r_usage );
struct rusage
{
    struct timeval ru_utime; //使用的用户时间(程序自身的指令消耗的时间)
    struct timeval ru_stime; //使用的系统时间(操作系统为程序执行所消耗的时间,包括I/O等系统调用、其它系统函数调用)
};
内存管理

C标准库函数可以用来分配内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdlib.h>
//注意,所有这些内存分配函数,都不保证两次调用分配的内存是连续的,不能假设并进行连续寻址
/**
* 分配指定大小的内存
*/
void *malloc( size_t size );
/**
* 用于为数组分配内存,所分配的内存全部初始化为0
* @param number_of_elements 数组元素个数
* @param element_size 单个元素的大小
* @return 指向第一个元素的指针
*/
void *calloc( size_t number_of_elements, size_t element_size );
/**
* 扩大或者减小已分配内存块的长度,可能需要移动整个内存块的位置
* 注意,一定要使用新的指针,而不是existing_memory的指针进行后续访问
* 如果无法调整内存块的大小,该函数返回空指针
*/
void *realloc( void *existing_memory, size_t new_size );

当物理内存耗尽时,内存分配会启用交换空间(Swap space),交换空间与Windows的虚拟内存交换文件类似,但是没有需要代码处理的局部堆、全局堆、可丢弃内存段等内容——Linux内核会管理所有工作。

Linux实现了“按需换页的虚拟内存系统”,程序看到的内存全部是虚拟的,Linux将内存按页划分,通常每页4096字节。

Linux内存管理系统能保护系统的其它部分免受错误使用内存的程序的影响,每个程序只能看到属于自己的内存映像,尝试访问不属于自己的内存空间,会导致段错误(Segmentation fault)。

空指针操作:glibc可能允许某些空指针读操作,输出“(null)\0”;空指针写、直接空指针读都是禁止的,会导致程序终止。

动态分配的内存在使用完毕后,应当调用free,将内存归还系统:

C
1
2
#include <stdlib.h>
void free(void *ptr_to memory);

free函数接受的指针必须指向由malloc、calloc、realloc调用所分配的内存。 

操控MySQL

下面的几个函数用于连接的初始化与关闭,以及读写数据:

C
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
/**
* 首先,需要使用该函数初始化MySQL连接句柄
* @param MYSQL* 使用既有的句柄,一般NULL
*/
MYSQL *mysql_init( MYSQL * );
/**
* 再正式连接之前,可以调用该函数提供一些选项,每次调用只能设置一个选项
* @param option_to_set 选项,支持:
*     const unsigned int * MYSQL_OPT_CONNECT_TIMEOUT  连接超时时间
*     NULL MYSQL_OPT_COMPRESS 启用网络流量压缩
*     const char * MYSQL_INIT_COMMAND 连接成功后指向的命令
*    
* @return 成功后返回0
*/
int mysql_options( MYSQL *connection, enum option_to_set, const char *argument );
/**
* 然后,提供参数进行连接
* @return 如果无法连接,返回NULL,可以通过mysql_error函数获取细节
*/
MYSQL *mysql_real_connect(
        MYSQL *connection, //指向已经由mysql_init初始化的结构
        const char *server_host, //主机名或者IP地址
        const char *sql_user_name, //MySQL用户名,为空则使用当前Linux的登录ID
        const char *sql_password, //密码,为空则只能访问不需要密码的资源
        const char *db_name, //使用的MySQL数据库
        unsigned int port_number, //如果使用默认端口,传递0
        const char *unix_socket_name, //一般传递NULL
        unsigned int flags ); //位或标记
/**
* 执行SQL语句
* @return 如果成功返回0
*/
int mysql_query( MYSQL *connection, const char *query );
//检查语句影响的行数
my_ulonglong mysql_affected_rows( MYSQL *connection );
//逐行提取结果集
MYSQL_RES *mysql_use_result( MYSQL *connection );
/**
 * 返回全部结果集
 * @return 如果成功,返回结果集指针,否则返回NULL
 */
MYSQL_RES *mysql_store_result( MYSQL *connection );
//上述函数调用成功后,应当调用下面的函数来获取结果集的行数
my_ulonglong mysql_num_rows( MYSQL_RES *result );
//读取下一行数据,如果没有更多的数据返回NULL
MYSQL_ROW mysql_fetch_row( MYSQL_RES *result );
//读取某个字段
MYSQL_FIELD *mysql_fetch_field( MYSQL_RES *result );
//可以在结果集中跳转到指定的偏移量,如果指定为0,那么下一次fetch_row返回第一行
void mysql_data_seek( MYSQL_RES *result, my_ulonglong offset );
//获取当前游标在结果集中的偏移量
MYSQL_ROW_OFFSET mysql_row_tell( MYSQL_RES *result );
//根据偏移量移动游标,返回先前的偏移量
MYSQL_ROW_OFFSET mysql_row_seek( MYSQL_RES *result, MYSQL_ROW_OFFSET offset );
/**
 * 返回结果集中字段的个数
 */
unsigned int mysql_field_count( MYSQL *connection );
//释放结果集相关资源
void mysql_free_result( MYSQL_RES *result );
 
/**
* 最后使用完毕后,需要关闭MySQL连接,该函数调用后,MySQL结构被释放,指针失效无法再次使用
*/
void mysql_close( MYSQL *connection );
 
/**
* 获取错误代码
*/
unsigned int mysql_errno( MYSQL *connection );
/**
* 获取错误文本,这些文本存放在MySQL内部静态空间
*/
char *mysql_error( MYSQL *connection );

示例代码:

C
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
setvbuf( stdout, NULL, _IONBF, 0 );
MYSQL* conn = mysql_init( NULL );
const unsigned int timeout = 5;
mysql_options( conn, MYSQL_OPT_CONNECT_TIMEOUT, ( char* ) &timeout );
if ( !mysql_real_connect( conn, "127.0.0.1", "root", "root", "test", 0, NULL, 0 ) )
{
    printf( "Failed to connect: %s\n", mysql_error( conn ) );
}
if ( !mysql_query( conn, "INSERT INTO T_CORP (CORP_NAME, CAPI) VALUES ('Gmem Inc.', 1000.20)" ) )
{
    printf( "Inserted %lu rows", ( unsigned long ) mysql_affected_rows( conn ) );
    mysql_query( conn, "SELECT LAST_INSERT_ID()" );
    MYSQL_RES *res_ptr = mysql_use_result( conn ); //使用结果集
    MYSQL_ROW sqlrow; //代表行的字符串数组
    while ( ( sqlrow = mysql_fetch_row( res_ptr ) ) ) //循环读取结果集
    {
        printf( ", Last Corp ID: %s", sqlrow[0] );
    }
    mysql_free_result( res_ptr ); //释放结果集
}
else
{
    printf( "Failed to execute SQL: %s\n", mysql_error( conn ) );
}
mysql_close( conn );
内核编程

参考:Linux内核编程知识集锦

← Linux IO编程
POSIX线程编程 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Linux进程间通信
  • Linux内核编程知识集锦
  • Linux信号、进程和会话
  • Linux IO编程
  • Linux网络编程

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
  • Three.js学习笔记 24 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2