NuttX的学习笔记 6

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yt454287063/article/details/53305903

在Ubuntu上安装了输入法,现在将博客编辑过程全部搬到Ubuntu上来。
现在已经明白了开发流程,下面换下一篇文档。比如在我的电脑上,目录如下:

/home/godenfreemans/nuttx/nuttx/Documentation/NuttxUserGuide.html

这是个网页格式的。看起来应该要比 txt 文本好得多。好的是Eclipse可以直接打开这个网页:

eclipse

但是鉴于出现了这样的情况:

出问题

还是把网址复制到浏览器中打开把。。
下面正式开始研究 NuttX 的系统接口。

先看一眼标题:(边看文档边做标题,看了一段时间后发现这标题长的有点吓人)


Introduction

This manual provides general usage information for the NuttX RTOS from the perspective of the firmware developer.

大概就是说这个文档是干嘛的。提供普通的说明。看样子还有其他不普通的说明啊。。。


OS Interfaces

This section describes each C-callable interface to the NuttX Operating System. The description of each interface is presented in the following format:

这个部分描述了 NuttX 的每个 C语言 的调用接口。每个接口都有如下格式:

  • Function Prototype
    函数原型
  • Description
    描述
  • Input Parameters
    输入参数
  • Returned Value
    返回值
  • Assumptions/Limitations
    假设和限制,不懂,实际使用中再分析功能。
  • POSIX Compatibility
    POSIX兼容性,可移植性操作系统接口,实际用处使用中再看。

Task Control Interfaces

任务控制接口

  • Tasks

    任务
    NuttX is a flat address OS. As such it does not support processes in the way that, say, Linux does. NuttX only supports simple threads running within the same address space. However, the programming model makes a distinction between tasks and pthreads

    NuttX是一个平坦地址操作系统,这里先了解一下平坦地址操作系统是什么。我查了很多资料,对于这个东西的说明很少,倒是查到了 Flat memory model 也就是平坦内存模型。我自己理解了一下,不知道对不对,经量避开一些术语表达:这属于内存的一种机制,在平坦地址中,CPU在内存中寻找一个地址,只需要只要其相对于内存开始地址的偏移量;还有一种叫做段地址,内存被分为几个段,所以,要在段地址下寻找一个地址,需要两个东西,一个是段号,一个是在该段中相对于段开头的偏移量。为在平坦地址下,32位总线的CPU能最大寻址:

    232bit=4294967296bit=4Gbit=0.5GB

    注意这里是4Gbit 而不是 4GB,为什么平时我们使用的32位桌面级CPU使用的内存可以支持超过0.5GB?我怀疑我是算错了,赶快去查,因为这和我大学所学的东西并不一样。结果出乎意料,并不是我计算错误。是因为这样一个错误的认识:32位CPU并不代表地址总线也是32位的。这么想想也是啊,8位的8051有16根地址总线,最大寻址 0xFFFF ;16位的MS430地址总线同样也为16根(我记得这家伙的内存的最大地址是 0xFFFF ,那无疑是16根的),那32位的桌面级CPU就不一定一定有32根地址总线。更何况,还有一个叫MMU的东西,可以管理更大的内存。关于这个我不多说,首先我并没有实际编写过程序驱动它或者调用过它。第二,STM32F429 处理器中并没有MMU,目前仔细研究也并没有实际作用。
    因为NuttX是平面地址操作系统,所以只能支持简单的线程在同一地址空间。不好意思,这里我并不明白其因果关系。。。我就直接理解为,NuttX 只能运行简单的线程。OK
    NuttX可以运行两种线程:

    • tasks
    • pthreads

    第一种比较独立,而第二种可以相互共享资源。

  • File Descriptors and Streams

    文件描述符和流。

    每创建一个tasks都会产生一组新的描述符包括 输入 , 输出 和 错误(?) (corresponding to stdin, stdout, stderr),子进程可以自由使用自己的描述符而不影响父进程。以此以做到相对独立。

    而pthreads与父进程共享相同的描述符。子进程的操作将影响到父进程和父进程所包括的所有子进程。以此做到互相共享资源。

  • Executing Programs within a File System

    在一个文件系统中执行程序。

    NuttX提供了内部接口用来执行驻留在文件系统中的二级制程序。这些接口是,当然,不标准的,也是记录在NuttX二进制加载器和the NuttX binary loader 和 NXFLAT。原谅我的英文没那么吊,这句话看起来相当混乱。啥叫不标准??不是理应提供标准的接口么???总之,就是可以在NuttX上运行一个二进制程序像在 Linux 上执行一个程序一样。这个好啊。

  • Task Control Interfaces

    任务控制接口

    Non-standard task control interfaces inspired by VxWorks interfaces:

    非标准的来自于 VxWorks 接口:

    task_create

    这个函数创建和激活一个新任务、指定优先级并返回其系统分配ID

    task_init

    该函数初始化一个任务控制块(TCB)准备启动一个新线程。它执行的功能的一个子集task_create()。
    不同于task_create(),task_init()不激活的任务,所以这个函数必须调用于task_activate()之前。

    task_activate

    这个函数用于激活由task_init()创建的任务。没有激活的任务不能被调度程序执行。

    task_delete

    被这个函数指定任务将不复存在,其栈和TCB将被收回。这个函数与task_create()成对出现。

    task_restart

    这个函数将重启任务。任务首先终止,然后被重新初始化并启动且使用相同的ID、优先级、原来的入口点、堆栈大小和参数。

    Standard interfaces

    标准接口:(上来就是个退出,这真的是标准接口?)

    exit
    getpid

    Standard vfork and exec[v|l] interfaces:

    标准 vfork 和 exec[v|l] 接口:

    vfork
    execv
    execl

    Standard posix_spawn interfaces:

    标准posix_spawn接口:

    posix_spawn and posix_spawnp
    posix_spawn_file_actions_init
    posix_spawn_file_actions_destroy
    posix_spawn_file_actions_addclose
    posix_spawn_file_actions_adddup2
    posix_spawn_file_actions_addopen
    posix_spawnattr_init
    posix_spawnattr_getflags
    posix_spawnattr_getschedparam
    posix_spawnattr_getschedpolicy
    posix_spawnattr_getsigmask
    posix_spawnattr_setflags
    posix_spawnattr_setschedparam
    posix_spawnattr_setschedpolicy
    posix_spawnattr_setsigmask

    Non-standard task control interfaces inspired by posix_spawn:

    来自posix_spawn非标准任务控制接口:

    task_spawn
    task_spawnattr_getstacksize
    task_spawnattr_setstacksize

    下面开始一个一个的测试,应该是一组一组的来。

    非标准的来自于 VxWorks 接口的实验

    刚刚上来就写,感觉有点无从下手,先从例程里找一个用过该函数的吧。
    我记得在按钮的例程中似乎有见到这个函数,寻找一番,果然:

    task_create

    但是并没有发现其他函数。看样子还是自己动手写一个好了。能够同时用到这些的程序,越简单越好。那就循环打印 hello world 好了。循环到一定次数后停止进程。

    首先要包含这个头文件: sched.h。

    
    #include <sched.h>
    

    接下来就是代码段了。
    (我大概浏览一遍我的这篇博客,突然发现这个有点像是在翻译文档一样……)
    首先来理一下这几个函数:

    任务 创建 启动 删除 重启
    task_create O O
    task_init O
    task_activate O
    task_delete O
    task_restart O

    那就先创建一个循环打印的函数,

    void print_test() {
        int a;
        for (a = 0; a < 10; a++) {
            printf("printf_test:%d\n", a + 1);
        }
    }

    在之前的的 hello world 中加入 task_create 的调用。关于这个参数,有点坑:

    task_create(char *name, int priority, int stack_size, main_t entry, char * const argv[]);

    *name还好说,任务名称嘛,字符串型。但是 priority 优先级,但是这里填什么?随便填个数?肯定不对啊。在 button的例程中的 Makefile 文件中发现了这个定义。

    CONFIG_EXAMPLES_BUTTONS_PRIORITY ?= SCHED_PRIORITY_DEFAULT

    全文件搜索 SCHED_PRIORITY_DEFAULT 最后在 ~/nuttx/nuttx/include/sys/types.h 文件中找到了定义:

     /* Scheduling Priorities.  NOTE:  Only the idle task can take the true
      * minimum priority.
      */
    
     #define SCHED_PRIORITY_MAX     255
     #define SCHED_PRIORITY_DEFAULT 100
     #define SCHED_PRIORITY_MIN       1
     #define SCHED_PRIORITY_IDLE      0

    这里果断选择 SCHED_PRIORITY_DEFAULT 。下一个是栈大小设置,就按照BUTTON的栈大小来设置吧,Makefile 中定义其栈大小为2048,这里也设定为2048。下一个是 main_t entry 主函数入口, print_test 填上。下一个,参数……看样子想写一个简单的程序还不行啊,那就干脆把BUTTON当成模板抄过来。
    button_daemon的模板大概是这样的:

    static int button_daemon(int argc, char *argv[]) {
    
        ...
        int ret;
        int fd;
    
        /* Indicate that we are running */
        g_button_daemon_started = true;
        printf("button_daemon: Running\n");
    
        /* Open the BUTTON driver */
        ...
    
        /* Get the set of BUTTONs supported */
        ...
    
        /* Now loop forever, waiting BUTTONs events */
        for (;;) {
        ...
        }
    
        /* Indicate that we are stopped */
    errout_with_fd:
        (void) close(fd);
    
    errout:
        g_button_daemon_started = false;
    
        printf("button_daemon: Terminating\n");
        return EXIT_FAILURE;
    }
    

    简单分析一下,全局申明任务已经运行了、任务内容及错误跳转和错误处理。明显这个任务不会停止。由于我要写的这个任务是会停止的,故这后面的错误也不会触发。所以要把任务改一改。这里,我要试试能不能做一个加法器。加法器也不会出发什么错误,那就做除法器吧。被除数为0则触发错误。

    然后,这个工程就再也没有 make 成功了。随便改一改倒是可以运行,但是到我输入数字的地方,就直接跳过了。scanf 函数根本不起作用。代码我也不放上来了。看样子还是欠了点什么。但是主要问题还是在 scanf 函数上。

    经过很长时间的调试,终于写出了可以运行的代码,理论上可以通过调用APP+参数 将创建一个打印参数值次数 hello world 的任务。但是实际情况并不如我所想。不能如意的代码:

    /*************************************
     * this function can translate string to number.
     ************************************/
    static int str2num(const char *s) {
        int rt = 0;
        while (*s >= '0' && *s <= '9') {
            rt = rt * 10 + (*s - '0');
            s++;
        }
        return rt;
    }
    
    /*************************************
     * print_hello
     ************************************/
    int print_hello(int argc, char *argv[]) {
        int a;
        int i;
        if(argc == 1){
            printf("add argc like:\n    hello_test 10\nwill print 10 times");
            return EXIT_SUCCESS;
        }
        a = str2num(argv[1]);
        printf("%d\n", a);
        for (i = 0; i < a; ++i) {
            printf("Hello World %d times", i + 1);
        }
        return EXIT_SUCCESS;
    }
    
    /*************************************
     * Public Functions
     ************************************/
    
    /************************************
     * hello_task_main
     ************************************/
    int hello_task_main(int argc, char *argv[]) {
        int ret;
        ret = task_create("hello_task", CONFIG_EXAMPLES_HELLO_TASK_PRIORITY,
        CONFIG_EXAMPLES_HELLO_TASK_STACKSIZE, print_hello, argv);
        printf("hello_task_main:task_create return:%d\n");
        return EXIT_SUCCESS;
    }

    运行结果就是:因为接收不到参数,所以一个printf都执行不了。
    所以说,我特地去查 argv 是没有设么意义的?顺便这里说一下 argc 和 argv 都是什么。
    argc 表示传入参数的个数,而 argv 则是存储参数的字符串数组。举个简单的例子,比如我下载程序时:

    cp nttx.bin /media/godenfreemans/DISCO-xxx

    argc = 3
    argv[0] = “/bin/cp”
    argv[1] = “/home/godenfreemans/nuttx/nuttx/nuttx.bin”
    argv[2] = “/media/godenfreemans/DISCO-xxx

    很好分析,执行cp时当前路径,第一个参数,第二个参数。在 NuttX 中,至少目前,我还没有找到这些 APP 在文件系统中的路径,所以 argv[0] 目前还没有什么用。

    然后就发现,我卡在 task_creat 上的时间太久了。将近有3天多没有什么进展了。难道没有点研究Linux的经验真的就搞不了这个?总之 task_create 算是能使用了吧。虽然最后的参数还有疑问,有空去拜读一下 PX4 的源码,看看他们是怎么用这个函数的。
    我决定还是继续接上这篇文档,argv的事情还没解决呢。首先看:

    int task_create(FAR const char *name, int priority, int stack_size,
            main_t entry, FAR char * const argv[]) {
        return thread_create(name, TCB_FLAG_TTYPE_TASK, priority, stack_size, entry,
                argv);
    }

    其实就是直接调用了 thread_create 函数。寻找定义:

    static int thread_create(FAR const char *name, uint8_t ttype, int priority,
                int stack_size, main_t entry, FAR char * const argv[]) {
        ;
        /* Setup to pass parameters to the new task */
        (void) task_argsetup(tcb, name, argv);
        ;
    }

    task_argsetup() ,问题又到这个函数上了。全文搜索才找到:

    static inline int task_stackargsetup(FAR struct task_tcb_s *tcb,
            FAR char * const argv[]) {
        FAR char **stackargv;
        FAR const char *name;
        FAR char *str;
        size_t strtablen;
        size_t argvlen;
        int nbytes;
        int argc;
        int i;
    
        /* Get the name string that we will use as the first argument */
    
    
    #if CONFIG_TASK_NAME_SIZE > 0
    
        name = tcb->cmn.name;
    
    #else
    
        name = (FAR const char *) g_noname;
    
    #endif /* CONFIG_TASK_NAME_SIZE */
    
    
        /* Get the size of the task name (including the NUL terminator) */
    
        strtablen = (strlen(name) + 1);
    
        /* Count the number of arguments and get the accumulated size of the
         * argument strings (including the null terminators).  The argument count
         * does not include the task name in that will be in argv[0].
         */
    
        argc = 0;
        if (argv) {
            /* A NULL argument terminates the list */
    
            while (argv[argc]) {
                /* Add the size of this argument (with NUL terminator).
                 * Check each time if the accumulated size exceeds the
                 * size of the allocated stack.
                 */
    
                strtablen += (strlen(argv[argc]) + 1);
                if (strtablen >= tcb->cmn.adj_stack_size) {
                    return -ENAMETOOLONG;
                }
    
                /* Increment the number of args.  Here is a sanity check to
                 * prevent running away with an unterminated argv[] list.
                 * MAX_STACK_ARGS should be sufficiently large that this never
                 * happens in normal usage.
                 */
    
                if (++argc > MAX_STACK_ARGS) {
                    return -E2BIG;
                }
            }
        }
    
        /* Allocate a stack frame to hold argv[] array and the strings.  NOTE
         * that argc + 2 entries are needed:  The number of arguments plus the
         * task name plus a NULL argv[] entry to terminate the list.
         */
    
        argvlen = (argc + 2) * sizeof(FAR char *);
        stackargv = (FAR char **) up_stack_frame(&tcb->cmn, argvlen + strtablen);
    
        DEBUGASSERT(stackargv != NULL);
        if (stackargv == NULL) {
            return -ENOMEM;
        }
    
        /* Get the address of the string table that will lie immediately after
         * the argv[] array and mark it as a null string.
         */
    
        str = (FAR char *) stackargv + argvlen;
    
        /* Copy the task name.  Increment str to skip over the task name and its
         * NUL terminator in the string buffer.
         */
    
        stackargv[0] = str;
        nbytes = strlen(name) + 1;
        strcpy(str, name);
        str += nbytes;
    
        /* Copy each argument */
    
        for (i = 0; i < argc; i++) {
            /* Save the pointer to the location in the string buffer and copy
             * the argument into the buffer.  Increment str to skip over the
             * argument and its NUL terminator in the string buffer.
             */
    
            stackargv[i + 1] = str;
            nbytes = strlen(argv[i]) + 1;
            strcpy(str, argv[i]);
            str += nbytes;
        }
    
        /* Put a terminator entry at the end of the argv[] array.  Then save the
         * argv[] arry pointer in the TCB where it will be recovered later by
         * task_start().
         */
    
        stackargv[argc + 1] = NULL;
        tcb->argv = stackargv;
    
        return OK;
    }

    并且不断地报着错,原因是声明函数类型的地方加了一个 inline 定义为:

     # define inline 1

    不明所以的定义。先不管这个,既然能编译,肯定有什么特殊的处理方法。
    删除 inline Eclipse 将不会再报错并可以高亮选中变量,选中 argvargv 主要出现在后面几段中,一段一段的分析。

    /* Count the number of arguments and get the accumulated size of the
         * argument strings (including the null terminators).  The argument count
         * does not include the task name in that will be in argv[0].
         */
    
        argc = 0;
        if (argv) {
            /* A NULL argument terminates the list */
    
            while (argv[argc]) {
                /* Add the size of this argument (with NUL terminator).
                 * Check each time if the accumulated size exceeds the
                 * size of the allocated stack.
                 */
    
                strtablen += (strlen(argv[argc]) + 1);
                if (strtablen >= tcb->cmn.adj_stack_size) {
                    return -ENAMETOOLONG;
                }
    
                /* Increment the number of args.  Here is a sanity check to
                 * prevent running away with an unterminated argv[] list.
                 * MAX_STACK_ARGS should be sufficiently large that this never
                 * happens in normal usage.
                 */
    
                if (++argc > MAX_STACK_ARGS) {
                    return -E2BIG;
                }
            }
        }

    看样子这里和我前面所查到的有冲突,这里,为了验证 argv[0] 中到底存放了什么,在传建任务后,使用两个printf 使用不同的输出格式将 argv[0] 中的数据输出。终端调试结果:

    nsh> hello_task
    hello_task_main:task_create return:536871940
    argc = 1
    argv[0] = <noname>
    argv[0] = <
    argv[1] = (null)
    0
    nsh> hello_task 10
    hello_task_main:task_create return:536871940
    argc = 2
    argv[0] = <noname>
    argv[0] = <
    argv[1] = 10
    0
    nsh>

    注释说 argv 是 task name ,但是这里是 noname 。我想这里的 noname 应该是因为我执行的这个 APP 不是一个 task 。那就把这些显示移到 task 里面:

    int print_hello(int argc, char *argv[]) {
        int a;
        int i;
        printf("argc = %d\n", argc);
        for (i = 0; i <= argc; ++i)
            printf("argv[%d] = %s\n",i, argv[i]);
        for (i = 0; i < a; ++i)
            printf("Hello World %d times", i + 1);
        return EXIT_SUCCESS;
    }

    这里顺便说一下 ++ii++ 的区别,我之前一直都是在用 i++ 昨天一朋友提醒我, ++ii++ 执行效率要高。因为还要找个寄存器备份原数据,这样一说还真是。
    编译运行APP。结果:

    nsh> hello_task
    hello_task_main:task_create return:536871940
    argc = 2
    argv[0] = <noname>
    argv[1] = <noname>
    argv[2] = (null)
    nsh> hello_task 10
    hello_task_main:task_create return:536871940
    argc = 3
    argv[0] = <noname>
    argv[1] = <noname>
    argv[2] = 10
    argv[3] = (null)
    nsh>

    这样看起来,参数是能够传递的,但是看起来父进城的 argv[0] 传递到子进程里去了。跳过 argv[0] 即可。总体代码改完是这样的。

    static int str2num(const char *s) {
        int rt = 0;
        while (*s >= '0' && *s <= '9') {
            rt = rt * 10 + (*s - '0');
            s++;
        }
        return rt;
    }
    
    int print_hello(int argc, char *argv[]) {
        int a;
        int i;
        a = str2num(arg[1]);
        for (i = 0; i < a; ++i)
            printf("Hello World %d times\n", i + 1);
        return EXIT_SUCCESS;
    }
    
    int hello_task_main(int argc, char *argv[]) {
        int ret;
        ret = task_create("hello_task", CONFIG_EXAMPLES_HELLO_TASK_PRIORITY,
        CONFIG_EXAMPLES_HELLO_TASK_STACKSIZE, print_hello, ++argv);
        printf("hello_task_main:task_create return:%d\n");
        return EXIT_SUCCESS;
    }

    运行结果:

    nsh> hello_task 10
    hello_task_main:task_create return:536871940
    Hello World 1 times
    Hello World 2 times
    Hello World 3 times
    Hello World 4 times
    Hello World 5 times
    Hello World 6 times
    Hello World 7 times
    Hello World 8 times
    Hello World 9 times
    Hello World 10 times
    nsh>

    好,完成。还有一件事需要解决,就是进程名是 <noname> 这个问题。既然有 <noname> 那么肯定可以找得到这串字符。

    ~/nuttx/nuttx/sched/task/task_setup.c

    在这个文件中,说明了为什么会是这样,首先,第78行:

    static const char g_noname[] = "<noname>";

    下来就是之前出现过的代码段:

    noname的由来
    我之所以放一张图片,是因为Eclipse已经用颜色提醒了我,之所以是 noname 是因为定义的 CONFIG_TASK_NAME_SIZE 小于零或者干脆就没有定义。(我收回这句话,貌似 .config 中的定义并不会被Eclipse检索到)寻找定义

     #define CONFIG_TASK_NAME_SIZE 0

    但是我想这个定义在configure里或许有,搜索一下果然有。

    果然有

    填个16,加入以下代码,编译运行:

    for (i = 0; i <= argc; ++i)
        printf("argv[%d] = %s\n", i, argv[i]);

    测试APP:

    nsh> hello_task 2
    hello_task_main:task_create return:536871960

    argv[0] = hello_task
    argv[1] = 2
    argv[2] = (null)
    hello_task_main:Hello World 1 times
    hello_task_main:Hello World 2 times
    print_hello:Hello World 1 times
    print_hello:Hello World 2 times
    nsh>

    果然可以输出进程名了。


这篇文博客编辑起来已经卡顿的很难继续进行下去了。到此先结束把。

猜你喜欢

转载自blog.csdn.net/yt454287063/article/details/53305903