Stories

Detail Return Return

redis源碼學習之makefile - Stories Detail

先從makefile入手,我這裏是基於redis 7.2.3的源碼。

# Top level makefile, the real shit is at src/Makefile

default: all

.DEFAULT:
    cd src && $(MAKE) $@

install:
    cd src && $(MAKE) $@

.PHONY: install

先來看看makefile這兩個偽指令:

  • .PHONY:
    目標“.PHONY”的所有的依賴被作為偽目標。偽目標時這樣一個目標:當使用make命令行指定此目標時,這個目標所在規則定義的命令、無論目標文件是否存在都會被無條件執行。
  • .DEFAULT
    Makefile 中,目標“.DEFAULT”所在規則定義的命令,被用在重建那些沒有具體規則的目標(明確規則和隱含規則)。就是説一個文件作為某個規則的依賴,但卻不是另外一個規則的目標時。Make 程序無法找到重建此文件的規則,此種情況時就執行“.DEFAULT”所指定的命令。

我們直接make,運行的是這個.DEFAULT目標,也就是 這一行cd src && $(MAKE) $@,也就是到src目錄下然後執行$(MAKE) $@.

這個$(MAKE)是什麼?

在Makefile中,$(MAKE) 是一個特殊的變量,用於遞歸調用 make 工具。在Makefile中,為了避免硬編碼make命令的名稱,通常使用$(MAKE)來代替。

這是因為在某些情況下,用户可能將 make 工具的可執行文件命名為不同的名稱,例如 GNU make 在不同系統上可能被安裝為make、gmake 或其他名稱。通過使用$(MAKE),Makefile可以使用調用者所使用的 make 工具的名稱,使其更具可移植性。

這個$@又是什麼?

在Makefile中,有一些特殊的自動變量:

我們以這一段makefile為例:

target: dependency1 dependency2
    command
  • $@
    代表規則中的目標文件(target)。它表示當前規則中的目標的文件名。例如:
    在這個規則中,command中的$@ 會被替換為 "target"。
  • $^
    代表規則中的所有依賴項(dependencies)。它表示所有出現在規則中的依賴項的列表,以空格分隔。例如:
    在這個規則中,command中的$^ 會被替換為 "dependency1 dependency2"。
  • $<
    代表規則中的第一個依賴項。例如
    在這個規則中,command中的$< 會被替換為 "dependency1"。
  • $*
    代表規則中目標的文件名(不包含文件擴展名)的部分。例如:

    %.o: %.c
      command $*

    在這個規則中,如果目標是 "example.o",$* 會被替換為 "example"。

回到上面的makefile中:cd src && $(MAKE) $@ 這一行相當於在src中執行make了。

去到src下的Makefile。這個makefile足足有500行,這裏就不貼出來了。

前面大部分都在定義一些變量,真正的make的目標從這裏開始:

all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) $(TLS_MODULE)
    @echo ""
    @echo "Hint: It's a good idea to run 'make test' ;)"
    @echo ""

這裏相當於如果執行make,將會編譯redis-server, redis-sentinel, redis-cli所有的這些目標。

我們現在只關心redis-server這一個。我們找到$(REDIS_SERVER_NAME) 這表明這個REDIS_SERVER_NAME也是一個變量,我們找到它的定義,它又使用了PROG_SUFFIX這個變量,但是PROG_SUFFIX默認是沒有定義的,所以其實相當於生成的目標文件就是redis-server

REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)

找到定義$(REDIS_SERVER_NAME)這個目標的地方

# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
    $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS)

-o $@ 表示生成的可執行文件就叫redis-server. $^代表規則中的所有依賴項(dependencies),也就是上面的$(REDIS_SERVER_OBJ),這些OBJ就是我們需要編譯的源碼了,就是下面這些文件了。

REDIS_SERVER_OBJ=threads_mngr.o 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 cluster_legacy.o crc16.o endianconv.o slowlog.o 
eval.o bio.o rio.o rand.o memtest.o syscheck.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 tracking.o socket.o 
tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o 
call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o 
connection.o unix.o logreqres.o

先看看這個$(REDIS_LD)變量

ifndef V
QUIET_CC = @printf '    %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2;
QUIET_GEN = @printf '    %b %b\n' $(CCCOLOR)GEN$(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
...

REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)

這裏的QUIET_LINK只是打印信息當前是編譯還是鏈接等,在Shell中,printf 是一個用於格式化輸出的命令。它的使用方式與C語言中的 printf 函數相似,但在Shell中,它用於在終端上格式化輸出文本。

$(CC) 是一個在Makefile中經常使用的變量,用於表示C編譯器的名稱。這個變量的默認值通常是 "cc",即C語言的默認編譯器。其實你在終端裏面輸入cc命令默認就是gcc。

在Makefile中,你可以使用 $(CC) 來引用C編譯器,從而使Makefile更具可移植性。這樣,如果用户在其系統上使用不同的C編譯器,只需更改 CC 變量的定義,而不必修改整個Makefile。

FINAL_LDFLAGS 是gcc的一些編譯參數。 比如-O3,表示GCC優化級別是3。

再回到這個target,其實就是將上面這些obj和這些.a鏈接成最終的redis-server可執行文件。FINAL_LIBS是一些系統的so鏈接庫列表。

# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
    $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a $(FINAL_LIBS)

現在有一個問題,這些.o文件又是怎麼生成的呢?下面這個目標會將當前目錄下所有的.c文件編譯成.o。

%.o: %.c .make-prerequisites
    $(REDIS_CC) -MMD -o $@ -c $<

Add a new Comments

Some HTML is okay.