当前位置 博文首页 > 想要去旅行:cgicc 上传大文件失败问题

    想要去旅行:cgicc 上传大文件失败问题

    作者:[db:作者] 时间:2021-08-31 15:58

    cgicc 上传大文件失败问题

    问题现象

    上传小文件正常,上传文件大于10M 时失败

    问题分析

    1,初步怀疑可用内存不够导致

    说明:当前板子的 RAM 大小为128M

    验证方法:更换大容量RAM 的板子

    验证结果:使用 RAM 大小为1G 的板子,上传大文件一切正常,说明就是内存不够。

    由此引出下面两个问题:

    1,同样是128M内存,为什么mt7688 平台没有问题?

    2,上传文件到底用了多少内存?

    不妨先看看 google 上怎么说:

    help-cgicc File upload memory usage

    While upgrading compilers from GCC 4.7 to GCC7.2 on an embedded device, I noticed a substantial increase in memory usage while parsing a file upload request.

    This seems to be caused by changed behavior in std::string. As GCC 4.7 was using copy-on-write and refcounting for std::string, a string copy did not cause a copy of the data. Now with GCC 7.2, a string copy also copies the data. For some large (+30MB) requests, this now makes our application go out-of-memory on a embedded device with only 128MB of RAM.

    提取有效信息:

    1,从 GCC4.7 升级到 GCC 7.2 后 ,才出了问题

    2, std::string 在GCC 4.7 中使用 copy-on-write ,a string copy did not cause a copy of the data,

    ? GCC 7.2, a string copy also copies the data

    我们H3 使用的GCC 版本是,GCC 7.3 , 而 MT7688 使用的 GCC 版本是GCC 4.8

    因此

    1,同样是128M内存,为什么mt7688 平台没有问题?

    原因是:GCC 版本不一样。

    继续了解:

    什么是 copy-on-write ?

    写时拷贝(copy-on-write) COW技术

    摘要:

    COW技术在C++中string实现上的应用

    看如下代码:

    string str1=“hello world”;
    string srr2=str1;
    此时str1和str2的存放地址其实是一样的

    之后执行
    str1[1]=‘a’;
    str2[1]=‘b’;

    执行修改后,此时str1的地址会发生变化,而str2的地址还是原来的

    ? 即在复制对象时,并不真正为新对象开辟内存空间,而是在新对象的内存映射表中设立一个指针,指向源对象,这样在进行读操作时因为并不修改对象,并不会给源对象带来影响,当某一时刻要对某一对象进行修改时,即写操作时,再将对象复制到新的内存空间中去,在这上面执行修改,以避免相互之间的影响。这样做的一个好处也是尽可能提高效率。
    ? 当然,不同的编译器string的实现方式不一样,有些编译器并不使用COW技术去实现

    延伸阅读 std:string 实现的三种方式

    • eager copy (直接拷贝) // 现在几乎不用
    • COW (copy on write) // c++98 标准中使用
    • SSO (small string optimization) // c++11 标准中使用

    详情可以看 《C++ 工程实践经验谈 by 陈硕 》第13章

    SSO 只对短字符串(通常是长度<15)起优化作用,而长字符串仍是要开辟新的内存空间。

    由此来看,对于小内存设备,不宜使用 SSO 实现方式。

    2,上传文件到底用了多少内存?

    采用SSO 方式,应该至少文件大小的 3 倍,

    1./tmp +1

    2.IO 和 CGICC 中的拷贝 +2

    3.代码其它地方可能还有拷贝 +x

    解决方法

    方法 1 :改GCC 版本为4.8

    OpenWrt 中可选的 gcc 版本最低为 5.x ,手动添加 4.8 版本过于麻烦,因此放弃。

    方法 2 :想办法禁用 c++11 特性,使std::tring 用回 COW

    google 到 一篇: c++ - gcc using c++11 standard even though 98 explicitly specified - Stack Overflow
    https://stackoverflow.com/questions/42514202/gcc-using-c11-standard-even-though-98-explicitly-specified

    有效信息

    I added -D_GLIBCXX_USE_CXX11_ABI=0 to the Makefile’s g++ command. That’s definitely the way to do it.

    在Makefile 中编译选项中加入 -D_GLIBCXX_USE_CXX11_ABI=0 。

    实测此方法可行!另外我们的文件上传本身就不是多线程设计,因此不必考虑线程不安全问题。

    总结梳理

    对于小内存设备,使用 c++ 的 sting 类不宜使用 SSO 方式,应使用COW 方式节省内存开销。

    gcc 5.x 版本之前使用 c++ 98 标准, 使用 COW 方式

    gcc 5.x 版本之后使用 c++ 11 标准,默认使用 SSO 方式 ,要使用 COW 方式需要编译时指定

    -D_GLIBCXX_USE_CXX11_ABI=0

    cs