この記事では、Redis に関する関連知識を提供し、主に、Makefile ファイル、src/Makefile ファイル、およびすべてのターゲットを含む、Redis ソース コード コンパイルにおける Makefile ファイルに関連する問題を紹介します。 -それに依存する目標やその他の関連コンテンツは、皆様のお役に立てれば幸いです。
推奨学習: Redis チュートリアル
Redis# が必要です。この記事 ##ソース コードを学習するには、
Makefile ファイルの実行プロセスを直観的に確認できるように、関連するコンパイル環境をセットアップすることをお勧めします。この記事「C の Redis 操作機能のカプセル化」には、
Redis のコンパイルとインストール方法が記載されています。ここで使用されるソース コードのバージョンは
redis-6.2.1 です。
Makefile ファイルの内容は以下の通りです:
default: all .DEFAULT: cd src && $(MAKE) $@install: cd src && $(MAKE) $@.PHONY: install
ですが、これには実際の効果はなく、
all ターゲット## に依存します。コード内には
# いわゆる make
を直接使用すると、default
ターゲットが最初に呼び出され、 all
ターゲットが呼び出されます。all
ターゲットが存在しないため、代わりに .DEFAULT
ターゲットが呼び出されます。 , $@
はターゲットの意味を表し、$(MAKE)
は make
を表すため、展開されたコードは次のようになります。読者は自分でコンパイルして確認できます。最初の出力ステートメントが分析したものと同じである場合
cd src && make all
Makefile
ファイルを呼び出します。唯一の違いは、この時点で呼び出されることです。ターゲットは install
になります。展開されたコードは次のとおりです。
cd src && make install
Makefile
の対応するターゲットを呼び出します。 #clean の例のコードは次のとおりです:
<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">cd src && make clean</pre><div class="contentsignin">ログイン後にコピー</div></div>
3 、src/Makefile ファイルの詳細説明3.1, Makefile.dep target
#Makefile
対応するターゲットを実行する前に、変数の代入、
ファイルは実行されないことがわかります。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">NODEPS:=clean distclean# FINAL_CFLAGS里的各个变量原型STD=-pedantic -DREDIS_STATIC=''WARN=-Wall -W -Wno-missing-field-initializers
OPTIMIZATION?=-O2
OPT=$(OPTIMIZATION)DEBUG=-g -ggdb#CFLAGS 根据条件选择的,不重要的参数,忽略#REDIS_CFLAGS 根据条件选择的,不重要的参数,忽略FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME)
@echo ""
@echo "Hint: It's a good idea to run 'make test' ;)"
@echo ""Makefile.dep:
-$(REDIS_CC) -MM *.c > Makefile.dep 2> /dev/null || trueifeq (0, $(words $(findstring $(MAKECMDGOALS), $(NODEPS))))-include Makefile.dep
endif</pre><div class="contentsignin">ログイン後にコピー</div></div>
まず以下の点を追加しますMakefile
basis#の使用形式
##Makefile の findstring
関数は
であり、IN
- 検索
"2"FIND を意味します。
、見つかった場合はFIND
を返し、見つからない場合は空を返しますMakefile
words
関数は単語数のカウントを表します。たとえば、$(words, foo bar)
の戻り値は ## of- CC
Makefile
#MAKECMDGOALS## です。 #変数は、(すべての)Makefile
の- に渡されるパラメータを表します。デフォルト値は
の ##-MMcc
## です。
##Makefile- は、ソース ファイルの依存関係を記述する
make
のルールを出力しますが、システム ヘッダー ファイルは含まれません
次の情報を要約できます:
- 里面的
all
目标正是我们前一节说到的那个默认的编译目标,但是我们可以自己试着去编译一下,会发现先生成的是Makefile.dep
文件,因为他先执行了最下面那个判断语句,里面调用了Makefile.dep
目标- 由于此时
MAKECMDGOALS
的值为all
,不在NODEPS
范围里,所以上面那个ifeq
语句成立,会调用Makefile.dep
目标REDIS_CC
的值由三个变量组成,QUIET_CC
是打印调试信息的,读者可以自己去源码看相关内容,这部分不重要,我们忽略,CC
的值代表的是编译器,FINAL_CFLAGS
里面的值则是编译的一些参数,这些值在上面的代码中都已经摘录出来了- 综上所述
Makefile.dep
目标的作用就是生成当前目录下所有以.c
结尾的文件的依赖关系,并写入Makefile.dep
文件中,编译之后生成的文件内容如下所示,看起来挺乱,但是里面的内容其实将每个源文件最终生成的目标文件给列出来,并且将它需要的依赖列出来而已acl.o: acl.c server.h fmacros.h config.h solarisfixes.h rio.h sds.h \ connection.h atomicvar.h ../deps/lua/src/lua.h ../deps/lua/src/luaconf.h \ ae.h monotonic.h dict.h mt19937-64.h adlist.h zmalloc.h anet.h ziplist.h \ intset.h version.h util.h latency.h sparkline.h quicklist.h rax.h \ redismodule.h zipmap.h sha1.h endianconv.h crc64.h stream.h listpack.h \ rdb.h sha256.h adlist.o: adlist.c adlist.h zmalloc.h ae.o: ae.c ae.h monotonic.h fmacros.h anet.h zmalloc.h config.h \ ae_epoll.c ae_epoll.o: ae_epoll.c... zipmap.o: zipmap.c zmalloc.h endianconv.h config.h zmalloc.o: zmalloc.c config.h zmalloc.h atomicvar.hログイン後にコピー3.2、通用的生成目标文件的target
代码如下:
.make-prerequisites: @touch $@ifneq ($(strip $(PREV_FINAL_CFLAGS)), $(strip $(FINAL_CFLAGS))).make-prerequisites: persist-settings endif ifneq ($(strip $(PREV_FINAL_LDFLAGS)), $(strip $(FINAL_LDFLAGS))).make-prerequisites: persist-settings endif %.o: %.c .make-prerequisites $(REDIS_CC) -MMD -o $@ -c $<ログイン後にコピー以下是对这部分代码的解析:
- 这部分是通用的根据源文件生成目标文件的
target
,Makefile
中%
表示通配符,所以只要符合格式要求的都可以借助这段代码来生成对应的目标文件.make-prerequisites
没啥用忽略,而REDIS_CC
的值在上一小节有说明了,是用于编译文件的指令gcc
的-MMD
参数与前面说的那个-MM
是基本一致的,只不过这个会将输出内容导入到对应的%.d
文件中Makefile
中$@
表示目标,$<
表示第一个依赖,$^
表示全部依赖- 综上,这个
target
的作用是依赖于一个源文件,然后根据这个源文件生成对应的目标文件,并且将依赖关系导入到对应的%.d
文件中下面是一个简单的例子:
# 假设生成的目标文件为acl.o,则代入可得acl.o: acl.c .make-prerequisites $(REDIS_CC) -MMD -o acl.o -c acl.c # 执行完成后在该目录下会生成一个acl.o文件和acl.d文件ログイン後にコピー3.3、all目标所依赖的各个子目标的名称设置
PROG_SUFFIX
的值默认为空,可以忽略。这里设置的六个目标名都是会被all
这个目标引用的,从名字可以看出这六个目标是对应着Redis
不同的功能,依次是服务、哨兵、客户端、基础检测、rdf持久化以及aof持久化。
代码如下:REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX) REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX) REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX) REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX) REDIS_CHECK_RDB_NAME=redis-check-rdb$(PROG_SUFFIX) REDIS_CHECK_AOF_NAME=redis-check-aof$(PROG_SUFFIX)ログイン後にコピー3.4、all目标所依赖的各个子目标的内容
REDIS_LD
也是一个编译指令,和前面那个REDIS_CC
有点像,只不过这个指定了另外的一些编译参数,比如设置了某些依赖的动态库、静态库的路径,读者有兴趣的话可以去看一下代码,看看REDIS_LD
的详细内容FINAL_LIBS
是一系列动态库链接参数,读者有兴趣可以自行去Makefile
里面查看该变量的内容,限于篇幅原因这里就不展开讲了- 将
QUIET_INSTALL
忽略(这个是自定义打印编译信息的),可以看出REDIS_INSTALL
的值其实就是install
,Linux
下的install
命令是用于安装或升级软件或备份数据的,这个命令与cp
类似,但是install
允许你控制目标文件的属性,这里不作深入分析了,有兴趣的读者可以自行查阅相关的介绍install
命令的文章。基本用法为:install src des
,表示将src
文件复制到des
文件去
代码如下:REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o release.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)-include $(DEP)INSTALL=install REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)# redis-server$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS)# redis-sentinel$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME) $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME)# redis-check-rdb$(REDIS_CHECK_RDB_NAME): $(REDIS_SERVER_NAME) $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_RDB_NAME)# redis-check-aof$(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME) $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)# redis-cli$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ) $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)# redis-benchmark$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ) $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)ログイン後にコピー3.4.1、REDIS_SERVER_NAME目标
该目标依赖于
REDIS_SERVER_OBJ
,而REDIS_SERVER_OBJ
的内容都是一些目标文件(上面代码有给出),这些目标文件最终都会通过3.2小节
介绍的那个target
来生成。可以看到REDIS_SERVER_NAME
这个target
需要使用REDIS_SERVER_OBJ
、…/deps/hiredis/libhiredis.a
、…/deps/lua/src/liblua.a
以及FINAL_LIBS
这些来编译链接生成最终的目标文件,即redis-server
3.4.2、REDIS_SENTINEL_NAME目标
可以看到
REDIS_SENTINEL_NAME
目标很简单,只是简单地使用install
命令复制了REDIS_SERVER_NAME
目标生成的那个文件,即redis-server
,从这里可以知道哨兵服务redis-sentinel
与Redis
服务使用的是同一套代码3.4.3、REDIS_CHECK_RDB_NAME目标
和前面的如出一辙,也是简单复制了
redis-server
文件到redis-check-rdb
文件去3.4.4、REDIS_CHECK_AOF_NAME目标
和前面的如出一辙,也是简单复制了
redis-server
文件到redis-check-aof
文件去3.4.5、REDIS_CLI_NAME目标
这个就不是简单复制了,而是使用和
REDIS_SERVER_NAME
目标相同的方法进行直接编译的,唯一的区别是REDIS_SERVER_NAME
链接了…/deps/lua/src/liblua.a
,而REDIS_CLI_NAME
链接的是…/deps/linenoise/linenoise.o
3.4.6、REDIS_BENCHMARK_NAME目标
这个也是使用和
REDIS_SERVER_NAME
目标相同的方法进行直接编译的,唯一的区别是REDIS_SERVER_NAME
链接了…/deps/lua/src/liblua.a
,而REDIS_BENCHMARK_NAME
链接的是…/deps/hdr_histogram/hdr_histogram.o
3.5、all目标
经过前面的介绍,
all
目标的作用也就一目了然了,最终会生成六个可执行文件,以及输出相应的调试信息
代码如下:all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) @echo "" @echo "Hint: It's a good idea to run 'make test' ;)" @echo ""ログイン後にコピー3.6、安装和卸载Redis的目标
3.6.1、安装Redis的目标
这里逻辑很简单,先创建一个用于存放
Redis
可执行文件的文件夹(默认是/usr/local/bin
),然后将REDIS_SERVER_NAME
、REDIS_BENCHMARK_NAME
、REDIS_CLI_NAME
对应的可执行文件复制到/usr/local/bin
中去,这里可以看到前面那几个照葫芦画瓢的文件并没有复制过去,而是直接通过创建软连接的方式去生成对应的可执行文件(内容相同,复制过去浪费空间)
代码如下:PREFIX?=/usr/local INSTALL_BIN=$(PREFIX)/bin install: all @mkdir -p $(INSTALL_BIN) $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN) $(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN) $(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)ログイン後にコピー3.6.2、卸载Redis的目标
这里就是删除前面复制的那些文件了,比较简单,就不细讲了
代码如下:uninstall: rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)}ログイン後にコピー3.7、clean和distclean目标
所有
Makefile
的clean
或者distclean
目标的作用都是大致相同的,就是删除编译过程中产生的那些中间文件,以及最终编译生成的动态库、静态库、可执行文件等等内容,代码比较简单,就不作过多的分析了
代码如下:clean: rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark rm -f $(DEP).PHONY: clean distclean: clean -(cd ../deps && $(MAKE) distclean) -(rm -f .make-*).PHONY: distcleanログイン後にコピー3.8、test目标
执行完
Redis
编译之后,会有一段提示文字我们可以运行make test
测试功能是否正常,从代码中我们可以看出其实不止一个test
目标,还有另一个test-sentinel
目标,这个是测试哨兵服务的。这两个目标分别运行了根目录的runtest
和runtest-sentinel
文件,这两个是脚本文件,里面会继续调用其他脚本来完成整个功能的测试,并输出测试信息到控制台。具体怎么测试的就不分析了,大家有兴趣的可以去看一下。
代码如下:test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) @(cd ..; ./runtest)test-sentinel: $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) @(cd ..; ./runtest-sentinel)ログイン後にコピー4、总结
本文详细地分析了与
Redis
编译相关的Makefile
文件,通过学习Makefile
文件里的内容,我们可以更为全面地了解Redis
的编译过程,因为Makefile
文件中将很多编译命令用@
给取消显示了,转而使用它自己特制的编译信息输出给我们看,代码如下:ifndef V QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; endifログイン後にコピー所以我们直接去编译的话很多细节会看不到,可以自己尝试修改
Makefile
文件,在前面这段代码之前定义V
变量,这样就可以看到完整的编译信息了。修改如下:V = 'good' ifndef V QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; endifログイン後にコピー本人之前也写过
Nginx
编译相关的文章,下面总结两者的几点区别:
Nginx
はShell
関連テクノロジを多く使用しますが、Redis
はこれらの- because
Nginx## をほとんど使用しません。 # クロスプラットフォーム関連のパラメーターは構成スクリプトを通じて構成されますが、
Redisはこれを
Makefileファイル内で直接実行します。この 2 つは異なります。利点と欠点は何ですか?
Nginxは主に、強力なスケーラビリティを実現するために非常に多くの構成スクリプトを使用しますが、
Redisは基本的にこれらを考慮する必要がないため、単純に実装するだけです
- Redis
Nginxはいくつかのスクリプトを配置します
Makefileファイル内のロジックを確認すると、
Nginxが最終的に生成されるようです。
Makefileこのファイルは、
Redis## よりもはるかにシンプルで理解しやすいです。 # (Nginx
複雑なロジックはこれらの構成スクリプト内にあります)
- 生成された構成ファイルには 1000 行の複数行があり、コードの量はそれよりはるかに多くなります。
Redis 学習チュートリアルNginx
はすべての依存関係のすべての生成メソッドをリストし、Redis
はMakefile.dep
とさまざまな # を使用するため、Redis
は 400 行よりも多くなります。 ##%.dファイルを使用して依存関係情報を中間ファイルに分散し、
Makefile推奨学習:
以上がRedis の古典的なテクニックの詳細な説明: Makefileの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。