对lwIP1.4.1的sys_arch.txt文件的翻译

lwIP,A Lightweight TCP/IP stack,是对TCP/IP协议栈的轻量级实现,主要用于资源紧张的嵌入式环境下,在尽量减少资源占用的前提下实现了TCP/IP协议栈的主要协议。sys_arch.txt是lwIP的系统仿真层说明文档,一般在移植lwIP时需要参考此文件的内容,笔者在查阅lwIP的相关书籍时,为了加深理解,尝试翻译了一下此文档。由于笔者查阅的书本附赠的lwIP附件为1.41版本,所以翻译的为1.4.1版本的sys_arch.txt文件。目前截至本文发布,最新的lwIP版本为2.12,与1.4.1版本不同,此版本下已无sys_arch.txt文件,而是改为由doxygen生成帮助文档,所以这份翻译应该仅供同样使用1.4.1版本的人参考了。

lwIP 0.6++ 的sys_arch接口

作者: Adam Dunkels

操作系统仿真层为lwIP代码与底层的操作系统内核之间提供了统一的接口。主旨是使得将lwIP移植到新的架构时,只需要改动一点点头文件并实现一个新的sys_arch(译者注:对应文件sys_arch.c以及sys_arch.h,这两个文件需要移植时实现)。脱离任何底层的操作系统来实现一个sys_arch也是有可能的。

sys_arch为lwIP提供信号量和邮箱。对于完整功能的lwIP来说,多线程支持通过sys_arch实现,但是基本的lwIP功能不需要这些。早先的lwIP版本同时需要在sys_arch中实现定时调度机制,但是从lwIP 0.5版本开始,这些在更高层次实现。

除了实现提供sys_arch功能的相关源文件,操作系统仿真层还需要提供一些头文件,这些头文件定义了lwIP中需要经常使用的宏。必须实现的文件以及宏定义在其后的sys_arch描述中列出。

信号量可以为计数信号量或者二值信号量,lwIP可以借助他们中任何一个正常工作。邮箱被用于消息传递,可以用允许一次传递多个消息的队列实现,也可以实现为一次只允许一个消息传递的rendez-vous点,lwIP可以借助他们中任何一个正常工作,但是前者会更有效率。邮箱中的消息除了一个指针之外,并无他物。

信号量由sys_arch.h中的sys_sem_t类型表示,而邮箱由sys_mbox_t类型表示。lwIP对于sys_sem_t以及sys_mbox_t类型的内部实现没有任何限制。

从lwIP 1.4.0版本开始,信号量和邮箱的相关函数被以一种既允许传入指针,也允许传入实际的系统结构体的方式来声明。这样一来,以上结构就可以实现就地分配内存(被放置于全局内存区或者栈内存区)或者分配于堆内存区中(“*_new()”函数中)。

如下函数必须由sys_arch实现:

  • void sys_init(void)
    被调用来初始化sys_arch层。
  • err_t sys_sem_new(sys_sem_t *sem, u8_t count)
    创建一个新的信号量。信号量被分配到“sem”所指的内存(sem可以是一个指针或者实际的系统结构体)。“count”参数用于指定信号量的初始状态(可以是0或1)。如果信号量被成功创建,ERR_OK应该被返回。返回的其他错误码可以指示出错的地方,但是断言除外,同时函数没有实现错误处理机制。
  • void sys_sem_free(sys_sem_t *sem)
    销毁一个信号量。
  • void sys_sem_signal(sys_sem_t *sem)
    发送一个信号。
  • u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
    堵塞线程并等待特定的信号产生。如果“超时”变量设置为非0,线程应该只会被堵塞特定的时长(单位为毫秒)。如果“超时”变量为0,线程将会被一直堵塞直到收到信号。
    如果超时参数非0,返回值为等待信号发生经过的毫秒时长。如果信号在特定时间内没有产生,返回值为SYS_ARCH_TIMEOUT。如果线程不需要等待信号参数(比如信号在执行函数时已经产生了),函数将会返回0。
    牢记lwIP用与sys_arch层相似的名称来实现函数,sys_sem_wait()函数实际调用了sys_arch中的sys_arch_sem_wait()函数。
  • int sys_sem_valid(sys_sem_t *sem)
    当信号量有效时返回1,否则返回0。当使用的是指针,只需要简单地判断指针是否指向NULL。当直接使用系统结构体,实现对信号量的检查会更复杂。此函数也可以实现为一个宏定义,此时函数不存在原型。
  • void sys_sem_set_invalid(sys_sem_t *sem)
    无效化一个信号量,这样使用sys_sem_valid()检查此信号量就会返回0。注意!这不意味着此信号量占用的空间会被释放,sys_sem_free()函数永远会在调用此函数之前被调用!此函数也可以实现为一个宏定义,此时函数不存在原型。
  • err_t sys_mbox_new(sys_mbox_t *mbox, int size)
    为最大大小的元素创建一个邮箱。存储在邮箱中的元素为指针。你必须在你的lwipopts.h中定义宏“_MBOX_SIZE”,或者也可以忽略这个参数改为使用默认大小。在邮箱创建成功后,ERR_OK应该被返回。其他错误码作为提示在发生除断言错误外的其他错误时被返回,但并没有已经实现好的错误处理机制。
  • void sys_mbox_free(sys_mbox_t *mbox)
    销毁一个邮箱。如果当邮箱被销毁时其中依然存在消息,这表明lwIP发生了程序错误,此时应该通知开发人员。
  • void sys_mbox_post(sys_mbox_t *mbox, void *msg)
    发送“消息”到邮箱。函数应该被堵塞直到“消息”发送完成。
  • err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
    尝试发送“消息”到邮箱,如果邮箱满了就返回ERR_MEM,如果发送成功就返回ERR_OK。
  • u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
    堵塞当前线程直到邮箱收到一条消息,但是线程的堵塞时间不会超过“timeout”超时毫秒数(就如同sys_arch_sem_wait()函数那样)。如果“timeout”为0,线程应该一直被堵塞直到一条消息到达。“msg”参数是由此函数设置的一个结果参数(通过“*msg = ptr”这种指针幅值的方式)。当“msg”为NULL时,表明此信息应该被丢弃。
    返回值与sys_arch_sem_wait()函数一模一样:等待消息到达经过的毫秒数,或者当遇到超时时返回SYS_ARCH_TIMEOUT。
    牢记sys_mbox_fetch()这个名称相似的函数是由lwIP实现的。
  • u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
    此函数与sys_arch_mbox_fetch函数相似,然而此函数会在邮箱中不存在消息时立刻返回SYS_MBOX_EMPTY。当获取成功时,此函数返回0。
    为了方便实现,此函数也可以被定义为类似于函数的宏定义而不是一个正常的函数。例如,一个简易的实现可以是如下这样:

    #define sys_arch_mbox_tryfetch(mbox,msg)\
    sys_arch_mbox_fetch(mbox,msg,1)

    尽管这样会带来不必要的延迟。
  • int sys_mbox_valid(sys_mbox_t *mbox)
    当邮箱有效时返回1,否则返回0。当使用指针时,简单的方法是检查指针是否为NULL。当直接使用系统结构体时,有效性的判定方法会更复杂。此函数也可以实现为宏定义,此时函数不存在原型。
  • void sys_mbox_set_invalid(sys_mbox_t *mbox)
    使一个邮箱无效化,此时sys_mbox_valid()函数返回0。注意!这不意味着邮箱所占空间将会被释放,sys_mbox_free()总是在调用此函数前被调用!此函数也可以被实现为一个宏定义,此时函数不存在原型。

如果底层操作系统支持多线程并且需要在lwIP中使用这些功能,那么如下函数也需要被实现:

  • sys_thread_t sys_thread_new(char name, void ( thread)(void *arg), void *arg, int stacksize, int prio)
    创建一个新的线程,此线程名为“name”,优先级为“pro”,执行于函数“thread()”中。“arg”参数将会作为函数参数传递给thread()函数。“stacksize”参数指定了线程占用的栈大小。新线程的id会被返回。id和优先级依赖于系统实现。
  • sys_prot_t sys_arch_protect(void)
    此可选函数用于进行一个“快速”临界区保护同时返回之前的保护等级。此函数只能在非常简短的临界区调用。一个基于中断驱动的嵌入式系统可能会通过禁用所有中断的方式来实现此函数。基于任务的系统可能会通过使用互斥量或者禁用任务来实现此函数。此函数应该支持在同一个任务或者中断中递归调用。换句话说,sys_arch_protect()可以在已经做了临界区保护的情况下被再次调用。在这种情况下返回值用于指示之前已经进行过保护了。
    sys_arch_protect()只在支持操作系统的情况下需要实现。
  • void sys_arch_unprotect(sys_prot_t pval)
    此可选函数对由pval指定的变量执行一系列“快速”临界区保护。查阅sys_arch_protect()相关的文档获取更多信息。此函数只在支持操作系统的情况下需要实现。

在某些配置下,你也需要实现以下函数:

  • u32_t sys_now(void)
    此可选函数以毫秒为单位返回当前的时间(不用考虑太多,此函数只用于求时间差值)。不实现此函数意味着你无法使用一些模块(比如:TCP时间戳、用于NO_SYS==1时的内部超时)。

注意:
需要小心地使用sys_arch中的mem_malloc()函数。当malloc()引用mem_malloc()时,可能会遇到循环函数调用问题。在mem.c中,mem_init()尝试使用mem_malloc来分配信号量空间,这显然无法在sys_arch使用mem_malloc时执行。

“系统支持”仿真层需要以下额外文件:

cc.h
- 架构环境,一些编译器特性,一些环境特性(或许需要移动一些环境相关的东西到sys_arch.h文件。)
被lwIP使用的以下类型定义-
u8_t, s8_t, u16_t, s16_t, u32_t, s32_t, mem_ptr_t
一些用于封装lwip的结构体的编译器关键字-
PACK_STRUCT_FIELD(x)
PACK_STRUCT_STRUCT
PACK_STRUCT_BEGIN
PACK_STRUCT_END
平台相关的诊断输出-
LWIP_PLATFORM_DIAG(x) -非致命错误,打印一条信息
LWIP_PLATFORM_ASSERT(x)-致命错误,打印信息并且中断执行
用于格式化打印的可移植宏定义-
U16_F, S16_F, X16_F, U32_F, S32_F, X32_F, SZT_F
”轻量级“同步机制-
SYS_ARCH_DECL_PROTECT(x) - 定义一个保护状态变量
SYS_ARCH_PROTECT(x) - 进入保护状态
SYS_ARCH_UNPROTECT(x) - 退出保护状态
如果编译器没有提供memset()函数,此文件必须实现此函数或包含一个实现了它的文件。
此文件还必须包含一个系统本地的,此文件定义了标准的*nix错误码,或者也可以添加#define LWIP_PROVIDE_ERRNO语句来使用arch.h文件中定义的错误码,这些错误码在lwIP中广泛使用。

perf.h
-架构相关的性能度量,测量在lwIP的各处被调用,但是这些相关宏也可以被定义为空。
PERF_START - 开始测量某个对象
PERF_STOP(x) - 停止测量某个对象并记录结果

sys_arch.h
-sys_arch.c的附属文件
架构依赖以下对象:
sys_sem_t, sys_mbox_t, sys_thread_t以及可选的sys_prot_t
定义用于将sys_mbox_t和sys_sem_t的值设置为NULL
SYS_MBOX_NULL NULL
SYS_SEM_NULL NULL