【p2p、分布式,区块链笔记 UPNP】: Libupnp test_init.c 02 初始化SDK --- UpnpInitPreamble

在这里插入图片描述

  • 该代码是用于初始化 UPnP(Universal Plug and Play)SDK 的初始步骤的实现。英文preamble /ˈpriːæmbl/ 意为n.序言;前言;导言;开场白;绪论 v. 作序言[绪论]。函数 UpnpInitPreamble() 负责执行一系列关键任务,以确保 UPnP SDK 在使用之前的各个组件都被正确配置和启动。

代码解析

/*! 
 * \brief 初始化过程如下 Performs the initial steps in initializing the UPnP SDK.
 *
 * \li **初始化 Winsock 库**(仅限 Windows)                Winsock library is initialized for the process (Windows specific). 
 * \li **初始化日志系统**,用于调试信息输出。                The logging (for debug messages) is initialized.
 * \li **初始化互斥锁、句柄表和线程池**,确保多线程环境中的安全和稳定。 Mutexes, Handle table and thread pools are allocated and initialized.
 * \li **设置 SOAP 和 GENA 的回调函数**,如果启用了这些功能。 Callback functions for SOAP and GENA are set, if they're enabled.
 * \li **初始化 SDK 的定时器线程**,用于定时任务。           The SDK timer thread is initialized.
 *
 * \return UPNP_E_SUCCESS on success.
 */

函数分析

static int UpnpInitPreamble(void) {
    int retVal = UPNP_E_SUCCESS;  // 初始化返回值为成功
    int i;                        // 循环变量
    
#ifdef UPNP_HAVE_OPTSSDP // FALSE
    uuid_upnp nls_uuid;           // 用于 SSDP 的 NLS UUID
#endif /* UPNP_HAVE_OPTSSDP */

    // WinsockInit根据平台初始化 Winsock 库(Windows 平台特有,Winsock 是 Windows 中用于网络编程的 API,定义在https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/api/upnpapi.c#L281-L319)
    //  Linux 上不需要显式地初始化网络库,也不需要像 Windows 上那样调用 WSAStartup() 和 WSACleanup()
    retVal = WinsockInit();
    if (retVal != UPNP_E_SUCCESS) {
        return retVal;  // Winsock 初始化失败,返回错误代码
    }

    // 通过时间来初始化随机数种子,用于 SDK 内部的随机数生成
    srand((unsigned int)time(NULL));

    // 初始化日志系统,用于调试输出
    retVal = UpnpInitLog();
    if (retVal != UPNP_E_SUCCESS) {
        // 如果日志系统初始化失败,返回通用初始化失败错误
        return UPNP_E_INIT_FAILED;
    }

    UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Inside UpnpInitPreamble\n");

    // 初始化 SDK 全局互斥锁
    retVal = UpnpInitMutexes();
    if (retVal != UPNP_E_SUCCESS) {
        return retVal;  // 互斥锁初始化失败,返回错误
    }

#ifdef UPNP_HAVE_OPTSSDP
    // 如果启用了 SSDP,生成并设置 NLS UUID
    uuid_create(&nls_uuid);
    upnp_uuid_unpack(&nls_uuid, gUpnpSdkNLSuuid);
#endif /* UPNP_HAVE_OPTSSDP */

    // 初始化句柄列表
    HandleLock();  // 加锁,防止多线程竞争
    for (i = 0; i < NUM_HANDLE; ++i) {
        HandleTable[i] = NULL;  // 将句柄表初始化为空
    }
    HandleUnlock();  // 解锁

    // 初始化 SDK 的全局线程池
    retVal = UpnpInitThreadPools();
    if (retVal != UPNP_E_SUCCESS) {
        return retVal;  // 线程池初始化失败,返回错误
    }

#ifdef INCLUDE_DEVICE_APIS // FALSE
    #if EXCLUDE_SOAP == 0
    // 如果启用了 SOAP,设置 SOAP 的回调函数
    SetSoapCallback(soap_device_callback);
    #endif
#endif /* INCLUDE_DEVICE_APIS */

#ifdef INTERNAL_WEB_SERVER
    #if EXCLUDE_GENA == 0
    // 如果启用了 GENA,设置 GENA 的回调函数
    SetGenaCallback(genaCallback);
    #endif
#endif /* INTERNAL_WEB_SERVER */

    // 初始化 SDK 定时器线程
    retVal = TimerThreadInit(&gTimerThread, &gSendThreadPool);
    if (retVal != UPNP_E_SUCCESS) {
        // 如果定时器线程初始化失败,调用 UpnpFinish 清理资源
        UpnpFinish();
        return retVal;
    }

    return UPNP_E_SUCCESS;  // 初始化成功
}

代码中的重要部分

1. Winsock 初始化 (WinsockInit):

  • 此函数用于初始化 Winsock 库(如果是Windows系统)。Winsock 是 Windows 中用于网络编程的 API。
  • WinsockInit根据平台初始化 Winsock 库(Windows 平台特有,Winsock 是 Windows 中用于网络编程的 API,定义在https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/api/upnpapi.c#L281-L319)
  • Linux 上不需要显式地初始化网络库,也不需要像 Windows 上那样调用 WSAStartup() 和 WSACleanup()。

2. 锁初始化资源 (UpnpInitMutexes):

  • 互斥锁(mutex)用于在线程间保护共享资源。这个函数初始化 SDK 的全局互斥锁,以确保资源访问的同步性。(比如在后续的uuid创建时,需要调用如下代码,先加锁,操作,后解锁)
extern ithread_mutex_t gUUIDMutex;

#define UUIDLock() ithread_mutex_lock(&gUUIDMutex)
#define UUIDUnlock() ithread_mutex_unlock(&gUUIDMutex)
  • 其中ithread_mutex_init、ithread_rwlock_init等函数同样是对POSIX线程库中的函数封装。
/*!
 * \brief 初始化 UPnP SDK 使用的全局互斥锁。
 *
 * \return UPNP_E_SUCCESS: 初始化成功
 *         UPNP_E_INIT_FAILED: 互斥锁初始化失败
 */
static int UpnpInitMutexes(void)
{
#ifdef __CYGWIN__
    // 在 Cygwin 系统上,pthread_mutex_init() 函数在某些情况下会失败。
    // 为了解决这个问题,需要在调用该函数之前将 GlobalHndRWLock 结构体清零。
    // 这是一个临时解决方案,未来应该修复 Cygwin 的这个 bug。
    memset(&GlobalHndRWLock, 0, sizeof(GlobalHndRWLock));
#endif

    // 初始化全局读写锁。
    // 读写锁允许多个线程同时读取共享数据,但同一时刻只能有一个线程写入。
    if (ithread_rwlock_init(&GlobalHndRWLock, NULL) != 0) {
        // 如果初始化失败,返回错误码。
        return UPNP_E_INIT_FAILED;
    }

    // 初始化全局 UUID 互斥锁。
    // 互斥锁确保同一时刻只有一个线程可以访问 UUID 生成相关的代码。
    if (ithread_mutex_init(&gUUIDMutex, NULL) != 0) {
        return UPNP_E_INIT_FAILED;
    }

    // 如果定义了 INCLUDE_CLIENT_APIS,则初始化全局订阅互斥锁。
    // 订阅互斥锁用于保护订阅相关的操作。
#ifdef INCLUDE_CLIENT_APIS
    if (ithread_mutex_init(&GlobalClientSubscribeMutex, NULL) != 0) {
        return UPNP_E_INIT_FAILED;
    }
#endif

    // 如果所有互斥锁都初始化成功,则返回成功。
    return UPNP_E_SUCCESS;
}

3. 句柄表HandleTable(SDK 内部资源的表)初始化:

  • 句柄表是用于管理 SDK 内部资源的表。在多线程环境下,必须加锁保护:
/*! UPnP device and control point handle table  */ // https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/api/upnpapi.c#L187-L188 
static void *HandleTable[NUM_HANDLE];
  • 这里首先通过 HandleLock() 函数加锁,然后初始化所有句柄为 NULL,最后通过 HandleUnlock() 解除锁定。
  • HandleLock()HandleUnlock() 定义如下:
/*!
 * \brief Get handle information.
 *
 * \return HND_DEVICE, UPNP_E_INVALID_HANDLE
 */
Upnp_Handle_Type GetHandleInfo(
	/*! handle pointer (key for the client handle structure). */
	int Hnd,
	/*! handle structure passed by this function. */
	struct Handle_Info **HndInfo);

#define HandleLock() HandleWriteLock()

#define HandleWriteLock() \
	UpnpPrintf( \
		UPNP_INFO, API, __FILE__, __LINE__, "Trying a write lock\n"); \
	ithread_rwlock_wrlock(&GlobalHndRWLock); \
	UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Write lock acquired\n");

#define HandleReadLock() \
	UpnpPrintf( \
		UPNP_INFO, API, __FILE__, __LINE__, "Trying a read lock\n"); \
	ithread_rwlock_rdlock(&GlobalHndRWLock); \
	UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Read lock acquired\n");

#define HandleUnlock() \
	UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Trying Unlock\n"); \
	ithread_rwlock_unlock(&GlobalHndRWLock); \
	UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Unlocked rwlock\n");

4.线程池初始化 (UpnpInitThreadPools):

  • 线程池用于处理 SDK 中的异步任务。UpnpInitThreadPools 函数确保线程池正确初始化,以便 SDK 能够高效处理任务。
/*!
 * \brief 初始化 UPnP SDK 所使用的全局线程池
 *
 * \return 成功时返回 UPNP_E_SUCCESS,若互斥锁无法初始化则返回 UPNP_E_INIT_FAILED
 */
static int UpnpInitThreadPools(void)
{
    // 定义返回值并初始化为成功标志
    int ret = UPNP_E_SUCCESS;
    
    // 定义线程池属性结构体
    ThreadPoolAttr attr;

    // 初始化线程池属性
    TPAttrInit(&attr);
    
    // 设置线程池的最大线程数
    TPAttrSetMaxThreads(&attr, MAX_THREADS);
    
    // 设置线程池的最小线程数
    TPAttrSetMinThreads(&attr, MIN_THREADS);
    
    // 设置线程栈的大小
    TPAttrSetStackSize(&attr, THREAD_STACK_SIZE);
    
    // 设置每个线程最多处理的任务数
    TPAttrSetJobsPerThread(&attr, JOBS_PER_THREAD);
    
    // 设置线程空闲的时间
    TPAttrSetIdleTime(&attr, THREAD_IDLE_TIME);
    
    // 设置线程池允许的最大任务总数
    TPAttrSetMaxJobsTotal(&attr, MAX_JOBS_TOTAL);

    // 初始化发送线程池,若失败则跳转到 exit_function 退出
    if (ThreadPoolInit(&gSendThreadPool, &attr) != UPNP_E_SUCCESS) {
        ret = UPNP_E_INIT_FAILED;
        goto exit_function;
    }

    // 初始化接收线程池,若失败则跳转到 exit_function 退出
    if (ThreadPoolInit(&gRecvThreadPool, &attr) != UPNP_E_SUCCESS) {
        ret = UPNP_E_INIT_FAILED;
        goto exit_function;
    }

    // 初始化迷你服务器线程池,若失败则跳转到 exit_function 退出
    if (ThreadPoolInit(&gMiniServerThreadPool, &attr) != UPNP_E_SUCCESS) {
        ret = UPNP_E_INIT_FAILED;
        goto exit_function;
    }

exit_function:
    // 如果初始化失败,设置 SDK 初始化状态为 0,并调用 UpnpFinish 清理
    if (ret != UPNP_E_SUCCESS) {
        UpnpSdkInit = 0;
        UpnpFinish();
    }

    // 返回初始化结果
    return ret;
}

5. 回调函数设置:

  • 如果启用了 SOAP 和 GENA 协议,分别设置它们的回调函数。这些函数用于处理设备 API 和事件通知。
5.1. SOAP(Simple Object Access Protocol)简单对象访问协议
  • SOAP 是一种基于 XML 的消息传输协议,设计用于在分布式网络环境中交换结构化信息。它常用于 web 服务中,尤其是在涉及复杂数据和远程过程调用 (RPC) 的场景下。
#ifdef INCLUDE_DEVICE_APIS
	#if EXCLUDE_SOAP == 0
	SetSoapCallback(soap_device_callback);
	#endif
#endif /* INCLUDE_DEVICE_APIS */
  • 函数SetSoapCallback(https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/genlib/miniserver/miniserver.c#L139)定义如下:
#ifdef INCLUDE_DEVICE_APIS
    void SetSoapCallback(MiniServerCallback callback) { gSoapCallback = callback; }
#endif /* INCLUDE_DEVICE_APIS */
  • 其传入的参数为定义为如下的函数指针类型:
/*! . */
typedef void (*MiniServerCallback)(
	/* ! [in] . 指向 http_parser_t 类型的指针,表示一个 HTTP 解析器,用于解析 HTTP 请求或响应 */
	http_parser_t *parser,
	/* ! [in] . 指向 http_message_t 类型的指针,表示一个 HTTP 请求消息,包含请求的详细内容。*/
	http_message_t *request,
	/* ! [in] .指向 SOCKINFO 类型的指针,表示套接字信息,可能包含有关网络连接的相关数据。 */
	SOCKINFO *info);
5.2. GENA(General Event Notification Architecture)通用事件通知架构
  • GENA 是一种用于异步事件通知的协议。它的主要目的是允许客户端订阅并接收来自服务器端的事件通知。GENA 协议最常见的使用是在 UPnP 环境中进行事件通知。
#ifdef INTERNAL_WEB_SERVER
	#if EXCLUDE_GENA == 0
	SetGenaCallback(genaCallback);
	#endif
#endif /* INTERNAL_WEB_SERVER */
  • 函数SetGenaCallback在https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/genlib/miniserver/miniserver.c#L142
void SetGenaCallback(MiniServerCallback callback) { gGenaCallback = callback; }

6.定时器线程初始化 (TimerThreadInit):

  • 这是 SDK 的最后一个初始化步骤,用于启动一个专门处理定时任务的线程。如果这个步骤失败,会调用 UpnpFinish() 来清理已经分配的资源。
  • 函数TimerThreadInit的主要作用是初始化一个定时器线程,并将其添加到指定的线程池中,以便定时器能够在线程池中异步执行任务。
  • 通过 TPJobInit 初始化线程池任务,并设置任务的高优先级,然后将任务添加到线程池中,确保定时器线程能够持续运行。
int TimerThreadInit(TimerThread *timer, ThreadPool *tp)
{
    // 定义返回值并初始化为 0,表示无错误
    int rc = 0;

    // 定义线程池任务结构体,代表定时器线程的工作
    ThreadPoolJob timerThreadWorker;

    // 断言 timer 和 tp 不为空,如果为空则程序中断
    assert(timer != NULL);
    assert(tp != NULL);

    // 如果 timer 或者线程池 tp 为空,返回 EINVAL 错误码
    if ((timer == NULL) || (tp == NULL)) {
        return EINVAL;
    }

    // 初始化定时器互斥锁,rc 累加返回值(0 为成功)
    rc += ithread_mutex_init(&timer->mutex, NULL);

    // 断言互斥锁初始化成功
    assert(rc == 0);

    // 锁定定时器的互斥锁
    rc += ithread_mutex_lock(&timer->mutex);
    assert(rc == 0);

    // 初始化定时器的条件变量
    rc += ithread_cond_init(&timer->condition, NULL);
    assert(rc == 0);

    // 初始化定时器事件的空闲列表(FreeList),每个事件的大小为 TimerEvent,容量为 100
    rc += FreeListInit(&timer->freeEvents, sizeof(TimerEvent), 100);
    assert(rc == 0);

    // 将定时器的关闭标志设为 0(未关闭)
    timer->shutdown = 0;

    // 设置定时器的线程池为传入的 tp
    timer->tp = tp;

    // 初始化最后一个事件 ID 为 0
    timer->lastEventId = 0;

    // 初始化事件队列(eventQ),事件队列存储定时器的所有事件
    rc += ListInit(&timer->eventQ, NULL, NULL);
    assert(rc == 0);

    // 如果初始化过程有错误(rc != 0),则返回 EAGAIN 错误码
    if (rc != 0) {
        rc = EAGAIN;
    } else {
        // 初始化线程池任务,指定工作函数 TimerThreadWorker,并传入定时器结构体
        TPJobInit(&timerThreadWorker, TimerThreadWorker, timer);

        // 设置线程池任务的优先级为高优先级
        TPJobSetPriority(&timerThreadWorker, HIGH_PRIORITY);

        // 将任务添加到线程池,作为一个持久任务运行
        rc = ThreadPoolAddPersistent(tp, &timerThreadWorker, NULL);
    }

    // 解锁定时器的互斥锁
    ithread_mutex_unlock(&timer->mutex);

    // 如果任务添加失败,则销毁已经初始化的资源(条件变量、互斥锁、空闲列表、事件队列)
    if (rc != 0) {
        ithread_cond_destroy(&timer->condition);
        ithread_mutex_destroy(&timer->mutex);
        FreeListDestroy(&timer->freeEvents);
        ListDestroy(&timer->eventQ, 0);
    }

    // 返回初始化的结果码,0 表示成功,非 0 表示错误
    return rc;
}
If you are experiencing problems with the Universal Plug and Play service, your computer might not be able to automatically detect the presence of other networked devices, such as PCs, printers, Internet access points and so on. That is where the UPnP Test application comes in. This simple program is designed to help you identify the issues that prevent the UPnP protocol from functioning correctly. Before you get your hopes up, you should know that this tool does not solve the detected problems, but only performs a series of tests to identify the possible causes. One advantage is that the application does not require installation, so your system registry is not affected in any way. The interface is compact and simple, comprising only two panels: one that displays the test type and its short description and the other for viewing which of the tests passed and which failed. The program can verify whether the operating system provides support for the UPnP service and allows you to check if the Simple Service Discovery Protocol (SSDP) and the UPnPHost services are running. It also verifies the connection between your network adapter and your router and the system's capacity to receive UPnP messages, as well as the router's capability to report an external IP address. One of the tests is designed to check if the Windows firewall service is blocking the traffic between your router and the system, thus preventing UPnP from working. The results can be copied to your clipboard by simply pressing a button and the tests can be redone easily. If you want to fix the detected issues, the link in the main interface can prove useful. In conclusion, UPnP Test is a simple tool for detecting problems related to device-to-device networking. However, it can only suggest possible reasons why the UPnP is not working, fixing the detected issues is totally up to you.
mips-linux-gnu-gcc -Os -pipe -march=mips32r2 -g -I/home/wyl/Documents/NVMP/nvmp/staging_dir/target-mips-openwrt-linux-uclibc-c510wv1/usr/lib/libiconv-full/include -I/home/wyl/Documents/NVMP/nvmp/staging_dir/target-mips-openwrt-linux-uclibc-c510wv1/usr/lib/libintl-full/include -DTAPO_USR_DEF_AUDIO_ALARM -DCONFIG_STATISTIC_REPORT_DOMAIN=n-da.tplinkcloud.com.cn -DTELEMETRY_SUPPORT -DPTZ_SUPPORT=gpio -DUPNPC_ENABLE -DOPTIONAL_ALARM_AUDIO -DMSG_PUSH_TO_ALEXA -DFACTORY_INFO_INTEGRITY_RESTORE_SUPPORT -DWLAN_MODULE_SUPPORT -DGZIP_SUPPORT -DBASE64_SUPPORT -DTELEMETRY_SUPPORT -DLOCAL_PROTOCOL_SECURE -DCLIENTS_CONN_MAX=4 -DHUB_MANAGE_SUPPORT -DCONFIG_MSG_PUSH_POST_URL=/api/v1 -DSENSITIVITY_INT -DMAKEROOM_BEFORE_UPGRADE -DAUDIO_ENABLE -DCONFIG_MOBILE_ACCESS_SET_SUPPORT -DCONFIG_SD_UPGRADE_FROM_MMCBLK0 -DSYSUPGRADE_CHECK_RSA -DCONFIG_TP_TAPO_SPMINIOS -DCONFIG_TP_TAPO_MAP_ROOTFS -DCDN_HTTPS_UPGRADE_SUPPORT -DAMAZON_FFS_SUPPORT -DTAPO_CARE_CDN_SUPPORT -DTP_TAPO -DCLOUD_IOT_SUPPORT -DCLOUD_IOT_SOUNDALARM_SUPPORT -DBIND_CODE_SUPPORT -DTP_TAPO_P2P_SHARE -DT43_CONFIG_CHECK_WHETHER_IS_CAL -DLENS_MASK_SUPPORT -DMULTI_MODEL -I/home/wyl/Documents/NVMP/nvmp/staging_dir/target-mips-openwrt-linux-uclibc-c510wv1/usr/include -I/home/wyl/Documents/NVMP/nvmp/staging_dir/target-mips-openwrt-linux-uclibc-c510wv1/include -I/home/wyl/Documents/NVMP/nvmp/../sdk/soc/t31x/uclibc-toolchain-0.9.33/mips-gcc472-glibc216-64bit/mips-linux-gnu/libc/uclibc/usr/include -I/home/wyl/Documents/NVMP/nvmp/staging_dir/target-mips-openwrt-linux-uclibc-c510wv1/usr/lib/libiconv-full/include -I/home/wyl/Documents/NVMP/nvmp/staging_dir/target-mips-openwrt-linux-uclibc-c510wv1/usr/lib/libintl-full/include -Wall -Werror -ffunction-sections -fdata-sections -DMODULE_LIST="\"tdpd tdpc tmpd mactool wirelesstool nifc dhcpc dhcps httpd sntpc onvif system miniupnpc upgrade telemetry cloud_iot remote_debugger hub_manage ffs\"" -I/home/wyl/Documents/NVMP/nvmp/build_dir/target-mips-openwrt-linux-uclibc-c510wv1/nsd/include -I/home/wyl/Documents/NVMP/nvmp/build_dir/target-mips-openwrt-linux-uclibc-c510wv1/nsd/modules -I/home/wyl/Documents/NVMP/nvmp/build_dir/target-mips-openwrt-linux-uclibc-c510wv1/nsd/common -I/home/wyl/Documents/NVMP/nvmp/build_dir/target-mips-openwrt-linux-uclibc-c510wv1/nsd/common/ds -I./libXml -I./libutils -I../../include -c -o hub_manage.o hub_manage.c hub_manage.c: In function 'set_hub_upnp_info': hub_manage.c:1279:3: error: implicit declaration of function 'UPNPC_ERROR' [-Werror=implicit-function-declaration] hub_manage.c:1311:3: error: implicit declaration of function 'sync_hub_timer_handler' [-Werror=implicit-function-declaration] hub_manage.c:1312:3: error: implicit declaration of function 'UPNPC_DEBUG' [-Werror=implicit-function-declaration] cc1: all warnings being treated as errors <builtin>: recipe for target 'hub_manage.o' failed make[5]: *** [hub_manage.o] Error 1 make[5]: Leaving directory '/home/wyl/Documents/NVMP/nvmp/build_dir/target-mips-openwrt-linux-uclibc-c510wv1/nsd/modules/hub_manage' Makefile:30: recipe for target 'build_modules' failed make[4]: *** [build_modules] Error 2 make[4]: Leaving directory '/home/wyl/Documents/NVMP/nvmp/build_dir/target-mips-openwrt-linux-uclibc-c510wv1/nsd' Makefile:574: recipe for target '/home/wyl/Documents/NVMP/nvmp/build_dir/target-mips-openwrt-linux-uclibc-c510wv1/nsd/.built' failed make[3]: *** [/home/wyl/Documents/NVMP/nvmp/build_dir/target-mips-openwrt-linux-uclibc-c510wv1/nsd/.built] Error 2 make[3]: Leaving directory '/home/wyl/Documents/NVMP/nvmp/tp_package/nsd' package/Makefile:105: recipe for target 'package/tp_package/nsd/compile' failed make[2]: *** [package/tp_package/nsd/compile] Error 2 make[2]: Leaving directory '/home/wyl/Documents/NVMP/nvmp' package/Makefile:101: recipe for target '/home/wyl/Documents/NVMP/nvmp/staging_dir/target-mips-openwrt-linux-uclibc-c510wv1/stamp/.package_compile' failed make[1]: *** [/home/wyl/Documents/NVMP/nvmp/staging_dir/target-mips-openwrt-linux-uclibc-c510wv1/stamp/.package_compile] Error 2 make[1]: Leaving directory '/home/wyl/Documents/NVMP/nvmp' /home/wyl/Documents/NVMP/nvmp/include/toplevel.mk:277: recipe for target 'world' failed make: *** [world] Error 2
最新发布
10-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值