nginx交叉编译流程


因工作需要对nginx进行交叉编译,在嵌入式平台上使用nginx以支持http2。但因为nginx原生不支持交叉编译,虽然新版本提供了–crossbuild的选项,但仍然会在交叉编译过程中遇到很多错误,如下记录结合网上汇总的各种解决过程,梳理的一次交叉编译的流程。主要分为四个环节,首先排除掉部分在执行configure生成makefile时就会出现的错误,保证makefile可以顺利生成;其次准备好相关的依赖库,主要包括pcre,zlib和openssl;接着就可以开始执行./configure命令去生成makfile,这里会讨论部分配置项在交叉编译时的具体作用和意义;最后生成的makefile还是需要做部分适配性修改,然后就可以执行 make ; make install来生成目标平台产物了。

使用的版本为nginx版本为1.18.0
编译平台为ubuntu14.04 x86环境。
目标平台为marvell平台 64位arm处理器环境。

1、生成Makefile前修改点

首先去除编译器的自检流程,./configure 执行时会检查当期编译的工具链是否有效,同时根据当前平台生成部分头文件,但因为需要执行交叉编译,部分检查手段就会存在异常,故而会在执行configure生成Makefile时就会报错,需要提前排除。

a) 修改 auto/cc/name 脚本,注释掉line21的exit 1
21    # exit 1

因脚本中执行 auto/feature 脚本中会使用 C C 交 叉 编 译 工 具 进 行 生 成 CC 交叉编译工具进行 生成 CCNGX_AUTOTEST 可执行文件尝试执行,但因为跨平台,势必无法执行成功,故而返回的ngx_found变量必为no,会导致退出,故而需要注释。

b)修改 auto/types/sizeof 脚本(该点需要适当注意)

该sizeof脚本会根据入参要判断的变量,比如int,long等,通过生成源文件,执行printf打印出大小,得到对应变量在当前平台的值大小,通过ngx_size,以及ngx_max_value等参数返回,得到一些平台相关变量的占位大小,以及最大值和范围等等,抽取脚本中的核心环节如下。

29     printf("%d", (int) sizeof($ngx_type));

调用的地方在auto/unix中,grep得到的清单如下:

./auto/unix:618:ngx_type="int"; . auto/types/sizeof
./auto/unix:620:ngx_type="long"; . auto/types/sizeof
./auto/unix:622:ngx_type="long long"; . auto/types/sizeof
./auto/unix:624:ngx_type="void *"; . auto/types/sizeof; ngx_ptr_size=$ngx_size
./auto/unix:636:. auto/types/sizeof
./auto/unix:651:ngx_type="size_t"; . auto/types/sizeof
./auto/unix:655:ngx_type="off_t"; . auto/types/sizeof
./auto/unix:659:ngx_type="time_t"; . auto/types/sizeof

该脚本中得到ngx_size后,会把名称和ngx_size或者ngx_max_value大小传给auto/types/value脚本,并以宏定义的形式写入ngx_auto_config.h脚本。从unix脚本中看,主要包括void *,sig_atomic_t,和time_t,size_t,off_t,time_t等,还是比较多的。这些对应的宏,以NGX_SIZE_T_LEN为例,还是在不少地方有使用到的。如下是对整个工程目录grep NGX_SIZE_T_LEN得到的结果

./auto/unix:653:ngx_param=NGX_SIZE_T_LEN; ngx_value=$ngx_max_len; . 
auto/types/value
./src/mail/ngx_mail_proxy_module.c:358:                   + 1 + NGX_SIZE_T_LEN 
+ 1 + 2;
./src/mail/ngx_mail_proxy_module.c:377:        line.len = s->login.len + 1 + 1 + 
NGX_SIZE_T_LEN + 1 + 2;
./src/core/ngx_log.c:600:                 + NGX_SIZE_T_LEN
./src/http/ngx_http_variables.c:787:    v->data = ngx_pnalloc(r->pool, 
NGX_SIZE_T_LEN);
./src/http/modules/ngx_http_image_filter_module.c:610:          + 2 * 
NGX_SIZE_T_LEN;
./src/http/modules/ngx_http_log_module.c:246:    { ngx_string("request_length"), 
NGX_SIZE_T_LEN,
./src/http/modules/ngx_http_scgi_module.c:773:    b = ngx_create_temp_buf(r->pool, 
NGX_SIZE_T_LEN + 1 + len + 1);
./objs/ngx_auto_config.h:211:#ifndef NGX_SIZE_T_LEN
./objs/ngx_auto_config.h:212:#define NGX_SIZE_T_LEN  
(sizeof("-9223372036854775808") - 1)

部分网上介绍说直接将$CC替换gcc,如果编译机和目标机的平台位数不一致的话,会产生问题,比如上述涉及到size相关的代码,其行为就会变得不可预知。同时将ngx_size变量写死,也是会产生一样的问题。
所以合理的方式是,参考auto/types/sizeof脚本中的判断方式,在目标平台上得到nginx所需的各个通过auto/types/sizeof脚本得到大小的变量的实际大小。
此处可能需要结合各人交叉编译的目标平台的实际结果进行判断,个人编写了如下源文件,可交叉编译放在目标平台上执行后查看结果。用于替换后续生成的ngx_auto_config.h源文件中的值。在执行./configure生成makefile时为保证通过,可以先修改$CC为gcc,后续再按如上方式进行替换。
[源文件待补充]
在我本地64位的目标机芯片上执行结果如下

# ./a.out 
name NGX_PTR_SIZE size 8
name NGX_SIG_ATOMIC_T_SIZE size 4
name NGX_SIZE_T_SIZE size 8
name NGX_OFF_T_SIZE size 8
name NGX_TIME_T_SIZE size 8
for 4 :
ngx_max_value 2147483647 
ngx_max_len (sizeof("-2147483648") - 1)
for 8 :
ngx_max_value 9223372036854775807LL 
ngx_max_len (sizeof("-9223372036854775808") - 1)

后续可与执行./configure后生成的ngx_auto_config.h中对照,看是否有需要修改之处
[这里可补充后续生成的结果]]

2、依赖库的准备

依赖库如开头所言,有三个,如下为当期编译中使用的依赖库版本,分别对要对依赖库也进行交叉编译的准备和流程做分析:

pcre-8.43
openssl-1.1.1d
zlib-1.2.11
a) pcre-8.43的编译接入进行分析

pcre库有对应的nginx在编译时可以包含的选项。

--with-pcre=DIR   #用于指定pcre库的源码位置
--with-pcre-opt=OPTIONS   #可以指定编译时加入的选项

实际最终在obj/Makefile中生成的编译pcre的执行的脚本如下

        cd /mnt/hd2/home/dir/nginxbuild/pcre-8.43 \
        && if [ -f Makefile ]; then $(MAKE) distclean; fi \
        && CFLAGS="-O2 -fomit-frame-pointer -pipe " \

但是—with-pcre-opt选项影响的是只 CFLAGS的选项,而不影响configure的参数,即无法配置交叉编译。
故而需要在上述位置进行手动修改,通过–host参数配置pcre库对应的交叉编译平台,注意此为通过./configure生成makefile后执行的,后续第四环节对生成的Makefile再做修改时会再提及。

1293:/mnt/hd2/home /nginxbuild/pcre-8.43/Makefile:   objs/Makefile
…
1297:./configure --host=aarch64-linux-gnu --disable-shared

执行configure前,保证下载了源码文件,并解压到对应位置,并将对应位置写入DIR即可。主要是生成obj/Makefile后需再做修改

b)openssl-1.1.1d库的编译接入分析

Nginx提供与openssl相关选项

--with-openssl #指定openssl的使用路径
--with-openssl-opt=OPTIONS #指定config使用的参数

比如带上了第三步调用configure生成makefile的参数,执行./configure命令配置nginx后,在obj/Makefile里面可以看到生成如下与openssl相关的目标和执行命令

/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d/.openssl/include/openssl/ssl.h:    objs/Makefile
    cd /mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d \
    && if [ -f Makefile ]; then $(MAKE) clean; fi \
    && ./config 
--prefix=/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d/.openssl no-shared 
no-threads linux-armv4 --cross-compile-prefix=aarch64-marvell-linux-gnu- 
--prefix=/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d/.openssl  no-asm 
no-ssl2 -DOPENSSL_USE_IPV6 -DOPENSSL_NO_DYNAMIC_ENGINE \
    && $(MAKE) \
&& $(MAKE) install_sw LIBDIR=lib

但是openssl源文件的config文件其实不支持接受指定平台参数,虽然此处交叉编译arm平台,故选择linux-armv4参数,即使指定,仍然会使用默认自身检测到的平台,在我的ubuntu14.04上就是如下地址:

Operating system: x86_64-whatever-linux2
Failure!  build file wasn't produced.
Please read INSTALL and associated NOTES files.  You may also have to look over
your available compiler tool chain or change your configuration.
target already defined - linux-x86_64 (offending arg: linux-armv4)

虽然–cross-compile-prefix参数的指定使得CC和AR等命令是正常的,但是在编译中会遇到-m64缺失的问题,原因因为认为目标平台是x86平台,gcc 使用-m64参数,用于确定编译的目标平台为64位还是32位,但是交叉编译工具链大概率不支持(我本地测试的两款是不支持的)

故而需要在执行最终编译前先进入到openssl的目录,提前执行Configure命令配置平台,Configure命令是接受平台参数的
指定生成目录直接为当前目录下的.openssl目录,因为为交叉编译,不应该install到系统目录下。

./Configure linux-armv4 --cross-compile-prefix=aarch64-marvell-linux-gnu- 
--prefix=/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d/.openssl no-shared 
no-asm no-ssl2 -DOPENSSL_USE_IPV6  -DOPENSSL_NO_DYNAMIC_ENGINE

以保证后续编译器使用正确的选项执行。这里暂不对openssl的配置命令做详述,有兴趣可以查阅相关资料。

c) zlib-1.2.11库的编译接入分析

zlib库最简单,只需要解压好,并放置在对应目录,带上 –with-zlib参数即可。

3、调用configure生成makefile

先一睹完整命令(实际使用注意需要在同一行中))

./configure 
--crossbuild=aarch-marvell-linux-gnu 
--with-cc=/opt/marvell_i686_gnu/bin//aarch64-marvell-linux-gnu-gcc 
--with-cpp=/opt/marvell_i686_gnu/bin//aarch64-marvell-linux-gnu-g++

--with-pcre=/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/pcre-8.43 
--with-openssl=/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d 
--with-openssl-opt="no-shared no-threads linux-armv4 --cross-compile-prefix=aarch64-marvell-linux-gnu- --prefix=/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d/.openssl  no-asm no-ssl2 -DOPENSSL_USE_IPV6 -DOPENSSL_NO_DYNAMIC_ENGINE" 
--with-zlib=/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/zlib-1.2.11 

--prefix=/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/output/  

--with-http_ssl_module 
--with-http_v2_module 

--without-http_upstream_zone_module 
--with-cc-opt="-D_GNU_SOURCE" 

上述配置项主要包括五个部分:1、交叉编译配置和工具链配置。2、依赖库配置。3、安装路径配置。4、部分http模块配置。5、部分编译错误处理。对1,2,3部分不再赘述,针对4,5的理由,做简单说明。

a)需支持http2和ssl

故而需要携带–with-http_ssl_module 和 --with-http_v2_module 选项。

b)GNU_SOURCE 选项

编译过程中可能遇到如下两类未定义问题
struct in6_pktinfo 结构体未定义
accept4 函数未定义
因为其定义依赖于GNU_SOURCE宏的包裹,故而需要添加。以accpet4的man手册为例。

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <sys/socket.h>
       int accept4(int sockfd, struct sockaddr *addr,
                   socklen_t *addrlen, int flags);

明确说明调用accept4要定义_GNU_SOURCE宏,故而携带–with-cc-opt="-D_GNU_SOURCE" 可以解决该问题。

c)–without-http_upstream_zone_module选项

该选项为解决如下编译问题

src/core/ngx_rwlock.c:125:2: error: #error ngx_atomic_cmp_set() is not defined!
4、生成makefile后修改点

执行./configure之后,最核心的生成产物在obj目录下,包括Makefile和相关头文件,针对这些产物还涉及到几个修改步骤。。注意后续每次执行./configure,这些内容都会重新生成,故而还是需要按照下述步骤再做一次修改。

a) pcre库修改

之前提到的pcre库修改,需修改为2a中1297行描述的内容,执行configure时带上指定host平台的参数。

b) 部分宏定义补充

需在ngx_auto_config.h添加如下宏定义,以解决后续可能出现的编译报错。

#ifndef NGX_SYS_NERR
#define NGX_SYS_NERR  132
#endif

#ifndef NGX_HAVE_SYSVSHM
#define NGX_HAVE_SYSVSHM 1
#endif
c) ngx_auto_config.h中size相关的修改。

如1b描述,需根据目标平台的实际情况,对ngx_auto_config.h中的部分相关宏参数做修改,比如NGX_PTR_SIZE等,这个需要根据自己实际情况做修改,无法给出一个通用的解决方案。如果目标平台与编译平台的位数一致,该点可能不需要改动。

d) 增加-pthread 参数。

带 pthread参数,才能索引到部分线程函数比如 pthread_atfork的定义,否则会报部分线程函数实现未定义错误。
修改在如下行

381     -ldl -lcrypt -pthread 
/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/pcre-8.43/.libs/libpcre.a 
/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d/.openssl/lib/libssl.a 
/mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/openssl-1.1.1d/.openssl/lib     /libcrypto.a 
-ldl /mnt/hd2/home/cpl/yac/nginxbuild_20210515_raw/zlib-1.2.11/libz.a \

最后执行make;make install即可生成对应目标平台的nginx产物。

5、参考链接

《深入理解Nginx:模块开发与架构解析》
https://blog.csdn.net/u011641885/article/details/49863723
https://blog.csdn.net/whahu1989/article/details/101567517
https://www.dazhuanlan.com/2019/12/09/5dee69b4131f9/

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值