25、嵌入式与实时应用中的Linux编程接口详解

嵌入式与实时应用中的Linux编程接口详解

1. 实时消息发送返回值

在实时消息发送操作中, rt_send_* 函数的返回值具有重要意义:
- 0 :表示消息已成功发送,无需回复。
- 非零值:表示任务期待一个回复。同时,不建议使用 rt_isrpc ,因为 rt_return 足够智能,能够自行判断是否需要回复。

2. rtai_fifos 模块

rtai_fifos 模块提供了一种点对点的顺序机制,用于内核空间实时任务与用户空间进程之间的通信。以下是该模块的主要函数:

函数名 功能 返回值
int rtf_create (unsigned int fifo, int size) 创建一个初始大小为 size 的实时 FIFO(RT - FIFO),并分配标识符 fifo 成功: size ;-ENODEV: fifo 大于或等于 RTF_NO ;-ENOMEM:无法为 RT - FIFO 分配 size 字节的内存
int rtf_destroy (unsigned int fifo) 关闭之前使用 rtf_create() rtf_open_sized() 创建/重新打开的实时 FIFO 成功:仍打开此 FIFO 的实时任务数量,0 表示 FIFO 真正关闭;-ENODEV: fifo 大于或等于 RTF_NO ;-EINVAL: fifo 不是有效的打开 FIFO
int rtf_reset (unsigned int fifo) 移除已发布但尚未从 fifo 中移除的任何数据 成功:0;-ENODEV: fifo 大于或等于 RTF_NO ;-EINVAL: fifo 不是有效的打开 FIFO
int rtf_resize (unsigned int fifo, int size) 修改之前使用 rtf_create() 创建的实时 FIFO 的大小为 size ,并丢弃当前 FIFO 中的任何数据 成功: size ;-ENODEV: fifo 大于或等于 RTF_NO ;-EINVAL: fifo 不是有效的打开 FIFO;-ENOMEM:无法为 RT - FIFO 分配 size 字节的内存
int rtf_put (unsigned int fifo, void *buf, int count) 将数据块写入之前使用 rtf_create 创建的实时 FIFO 成功:写入的字节数;-ENODEV: fifo 大于或等于 RTF_NO ;-EINVAL: fifo 不是有效的打开 FIFO
int rtf_get (unsigned int fifo, void *buf, int count) 从之前使用 rtf_create 创建的实时 FIFO 中读取数据块 成功:读取的字节数;-ENODEV: fifo 大于或等于 RTF_NO ;-EINVAL: fifo 不是有效的打开 FIFO
int rtf_create_handler (unsigned int fifo, int (*handler)(unsigned int fifo));
int rtf_create_handler (unsigned int fifo, X_FIFO_HANDLER(handler));
安装一个处理程序,当数据写入或从实时 FIFO 读取时执行 成功:0;-EINVAL: fifo 不是有效的打开 FIFO

以下是 rtf_create 函数的使用示例:

#include <rtai_fifos.h>

int main() {
    unsigned int fifo = 1;
    int size = 1024;
    int result = rtf_create(fifo, size);
    if (result == size) {
        // 创建成功
    } else {
        // 处理错误
    }
    return 0;
}
3. rtai_shm 模块

rtai_shm 模块支持内核空间 RTAI 任务与用户空间进程之间的共享内存区域。

3.1 内核空间 API
  • void *rtai_kmalloc (unsigned long name, int size) :分配一个名为 name 、大小为 size 字节的共享内存区域。首次调用为给定名称分配空间,后续调用(无论是内核空间还是用户空间)只需附加到该区域。
    • 成功:返回分配空间的地址。
    • 0:无法分配请求的空间。
  • void rtai_kfree (void *adr) :释放由 name 标识的共享内存区域。实际上,在最后一个附加到该区域的进程/任务调用 rtai_kfree 之前,该区域只是被取消映射。
3.2 用户空间 API
  • void *rtai_malloc (unsigned long name, int size) :分配一个名为 name 、大小为 size 字节的共享内存区域。
  • void *rtai_malloc_adr (void *adr, unsigned long name, int size) :请求分配一个特定地址的共享内存区域,但这只是一个“建议”,返回值可能与 adr 相同,也可能不同。
    • 成功:返回分配空间的地址。
    • 0:无法分配请求的空间。
  • void rtai_free (unsigned long name, void *adr) :释放由 name 标识的共享内存区域。
4. 实用函数
  • unsigned long nam2num (const char *name) :将最多六个字符的 ASCII 名称转换为无符号长整型 ID。有效字符集为 'A' 'Z' 'a' 'z' (不区分大小写)、 '0' '9' '_' ,其他字符将被转换为 '$'
    • 成功:返回转换后的 ID。
    • 无错误条件。
  • void num2nam (unsigned long id, char *name) :使用与 nam2num 相同的算法将 ID 转换回 ASCII 名称字符串。
5. rtai_lxrt 模块

rtai_lxrt 模块允许用户空间进程(几乎)透明地使用 RTAI API。以下是该模块中与内核空间 API 不同的函数:

5.1 对象初始化
  • RT_TASK *rt_task_init (unsigned int id, int priority, int stack_size, int max_msgsize) :在用户空间创建一个名为 id 的新实时任务,具有指定的优先级。 stack_size max_msgsize 可以为 0,此时使用默认值(默认栈大小为 512 字节,默认最大消息大小为 256 字节)。
    • 成功:返回指向内核空间任务结构的指针,该值不能在用户空间直接使用,只能作为参数传递给其他 LXRT 函数。
    • 0:无法创建伙伴任务或 id 已在命名空间中注册。
  • SEM *rt_sem_init (unsigned long id, int initial_count) :分配并初始化一个名为 id 、初始值为 initial_count 的信号量。
    • 成功:返回指向内核空间信号量结构的指针,该值不能在用户空间直接使用,只能作为参数传递给其他 LXRT 函数。
    • 0:无法创建信号量或 id 已在命名空间中注册。
  • MBX *rt_mbx_init (unsigned long id, int size) :分配并初始化一个名为 id 、大小为 size 的邮箱。
    • 成功:返回指向内核空间邮箱结构的指针,该值不能在用户空间直接使用,只能作为参数传递给其他 LXRT 函数。
    • 0:无法创建邮箱或 id 已在命名空间中注册。
5.2 内核空间命名空间实用函数
  • int rt_register (unsigned long id, void *adr) :将名称 id 与内核空间对象(由 adr 指向)关联,并将该对象注册到 LXRT 命名空间中,以便用户空间 LXRT 进程可以引用该对象。
    • 成功:返回正整数(实际上是命名空间表中的槽号)。
    • 0:无法注册对象,命名空间表已满。
  • int rt_drg_on_adr (void *adr) int rt_drg_on_name (unsigned long id) :从命名空间中移除对象,可以通过地址( adr )或名称( id )引用要移除的对象。
    • 成功:返回正整数(实际上是对象在命名空间表中占用的槽号)。
    • 0:无法注销对象,未在命名空间表中找到。
5.3 用户空间命名空间实用函数
  • void *rt_get_adr (unsigned long id) :返回名为 id 的对象的地址。
  • unsigned long rt_get_name (void *adr) :返回地址为 adr 的对象的名称。
    • 两个函数在请求的对象不在命名空间中时都返回 0。
5.4 用户空间硬实时
  • void rt_make_hard_real_time (void) :使用户空间进程具有硬实时特性,进程必须锁定在内存中。硬实时进程应避免进行任何可能导致任务切换的 Linux 系统调用。只有 root 用户运行的进程才能锁定内存,但可以使用 rt_allow_nonroot_hrt() 允许非 root 用户锁定内存并调用硬实时。
  • void rt_make_soft_real_time (void) :使进程恢复正常的软实时行为。
  • void rt_allow_nonroot_hrt (void) :允许非 root 用户锁定内存并调用硬实时。
6. Posix 线程(Pthreads)API

RTAI 支持 Posix 1003.1c - 1995 的部分特性。Pthreads 函数通常返回一个整数状态码,0 表示函数成功,负数表示错误条件。Pthreads 区分两种类型的错误:
- 强制性(“如果发生”)错误:涉及程序员无法控制的情况,系统必须始终检测并报告此类错误。
- 可选性(“如果检测到”)错误:通常是程序员的错误,系统可能因处理器时间或其他系统资源成本过高而无法检测和报告所有潜在错误条件。一些系统可能提供“调试”模式,在开发代码时打开这些可选错误,发布生产代码时关闭调试模式。

以下是 Pthreads 相关函数的介绍:

6.1 线程操作
  • int pthread_create (pthread_t *tid, const pthread_attr_t *attr, void *(*start) (void *), void *arg) :创建一个新线程,具有可选的创建属性 attr 。新线程执行函数 start ,并传递参数 arg
    • 错误:
      • EINVAL attr 无效。
      • EAGAIN :没有足够的资源来创建线程。
  • int pthread_exit (void *ret_val) :终止调用线程,先调用任何注册的清理处理程序, ret_val 返回给任何加入该线程的线程。
  • pthread_t pthread_self (void) :返回调用线程的 ID。
  • int sched_yield (void) :让出处理器,只有在该优先级级别的所有其他就绪线程都运行后,该线程才准备好运行。
graph TD;
    A[开始] --> B[创建线程];
    B --> C{线程执行};
    C --> D[线程退出];
    D --> E[结束];
6.2 线程属性
  • int pthread_attr_init (pthread_attr_t *attr) :初始化一个线程属性对象,使用默认值。
    • 错误: ENOMEM :没有足够的内存用于属性对象。
  • int pthread_attr_destroy (pthread_attr_t *attr) :销毁一个线程属性对象。
    • 错误: EINVAL attr 不是线程属性对象。
  • int pthread_attr_getdetachstate (const pthread_attr_t *attr, int *detach_state) int pthread_attr_setdetachstate (pthread_attr_t *attr, int detach_state) :获取或设置线程的“分离”状态。
    • detach_state
      • PTHREAD_CREATE_JOINABLE
      • PTHREAD_CREATE_DETACHED (默认)
    • 错误:
      • EINVAL attr 不是线程属性对象。
      • EINVAL detach_state 无效(仅设置时)。
6.3 调度策略属性
  • int pthread_attr_getinheritsched (const pthread_attr_t *attr, int *inherit_sched) int pthread_attr_setinheritsched (pthread_attr_t *attr, int inherit_sched) :确定使用此属性创建的线程是继承调用者的调度策略和参数,还是使用 attr 中的参数。
    • inherit_sched
      • PTHREAD_INHERIT_SCHED
      • PTHREAD_EXPLICIT_SCHED (默认)
    • 错误:
      • ENOSYS :不支持优先级调度。
      • EINVAL attr 不是线程属性对象或 inherit_sched 无效(仅设置时)。
  • int pthread_attr_getschedparam (const pthread_attr_t *attr, struct sched_param *sched_p) int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *sched_p) :获取或设置使用此属性创建的线程的调度参数。
    • 错误:
      • ENOSYS :不支持优先级调度。
      • EINVAL attr 不是线程属性对象或 sched_p 无效(仅设置时)。
  • int pthread_attr_getschedpolicy (const pthread_attr_t *attr, int *policy) int pthread_attr_setschedpolicy (pthread_attr_t *attr, int policy) :获取或设置使用此属性创建的线程的调度策略。
    • Policy
      • SCHED_FIFO
      • SCHED_RR
      • SCHED_OTHER (默认)
    • 错误:
      • ENOSYS :不支持优先级调度。
      • EINVAL attr 不是线程属性对象或 policy 无效(仅设置时)。
  • int pthread_attr_getscope (const pthread_attr_t *attr, int *scope) int pthread_attr_setscope (pthread_attr_t *attr, int scope) :获取或设置使用此属性创建的线程的“竞争范围”。
    • scope
      • PTHREAD_SCOPE_PROCESS
      • PTHREAD_SCOPE_SYSTEM (默认)
    • 错误:
      • ENOSYS :不支持优先级调度。
      • EINVAL attr 不是线程属性对象或 scope 无效(仅设置时)。
6.4 互斥锁操作
  • int pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *mutex_attr) :创建一个互斥锁对象,如果 mutex_attr 不为空,则提供可选属性。
    • 错误:
      • EAGAIN :除内存外的资源不足。
      • ENOMEM :没有足够的内存用于互斥锁对象。
      • EPERM :没有权限执行此操作。
      • EBUSY :互斥锁已初始化。
      • EINVAL mutex_attr 不是互斥锁属性对象。
  • int pthread_mutex_destroy (pthread_mutex_t *mutex) :销毁不再需要的现有互斥锁。
    • 错误:
      • EBUSY :互斥锁正在使用中。
      • EINVAL mutex 不是互斥锁。
  • int pthread_mutex_lock (pthread_mutex_t *mutex) :锁定互斥锁。如果互斥锁已锁定,调用线程将被阻塞,直到互斥锁被解锁。
    • 错误:
      • EINVAL :线程优先级超过互斥锁优先级上限。
      • EINVAL mutex 不是互斥锁对象。
      • EDEADLK :调用线程已经拥有互斥锁。
  • int pthread_mutex_trylock (pthread_mutex_t *mutex) :尝试锁定互斥锁,如果互斥锁当前未锁定,则锁定;如果已锁定,则立即返回错误代码。
    • 错误:
      • EINVAL :线程优先级超过互斥锁优先级上限。
      • EBUSY :互斥锁已锁定。
      • EINVAL mutex 不是互斥锁对象。
      • EDEADLK :调用线程已经拥有互斥锁。
  • int pthread_mutex_unlock (pthread_mutex_t *mutex) :解锁互斥锁,如果有线程在等待该互斥锁,其中一个线程将被唤醒并成为新的所有者。
    • 错误:
      • EINVAL mutex 不是互斥锁。
      • EPERM :调用线程不拥有互斥锁。
6.5 互斥锁属性
  • int pthread_mutexattr_init (pthread_mutexattr_t *mutex_attr) :初始化一个互斥锁属性对象,使用默认值。
    • 错误: ENOMEM :没有足够的内存用于属性对象。
  • int pthread_mutexattr_destroy (pthread_mutexattr_t *mutex_attr) :销毁一个互斥锁属性对象。
    • 错误: EINVAL mutex_attr 不是互斥锁属性对象。
  • int pthread_mutexattr_getkind_np (const pthread_mutexattr_t *mutex_attr, int *kind) int pthread_mutexattr_setkind_np (pthread_mutexattr_t *mutex_attr, int kind) :获取或设置互斥锁的“类型”。
    • kind
      • PTHREAD_MUTEX_FAST_NP
      • PTHREAD_MUTEX_RECURSIVE_NP
      • PTHREAD_MUTEX_ERRORCHECK_NP
    • 错误:
      • EINVAL mutex_attr 不是互斥锁属性对象。
      • EINVAL kind 无效(仅设置时)。
  • int pthread_mutexattr_getprioceiling (const pthread_mutexattr_t *mutex_attr, int *prioceiling) int pthread_mutexattr_setprioceiling (pthread_mutexattr_t *mutex_attr, int prioceiling) :获取或设置使用 attr 创建的互斥锁的优先级上限,RTAI Pthreads 中未实现。
    • 错误:
      • ENOSYS :不支持优先级调度。
      • EINVAL attr 不是互斥锁属性对象或 prioceiling 无效(仅设置时)。
      • ENOPERM :没有权限设置优先级上限。
  • int pthread_mutexattr_getprotocol (const pthread_mutexattr_t *mutex_attr, int *protocol) int pthread_mutexattr_setprotocol (pthread_mutexattr_t *mutex_attr, int protocol) :获取或设置互斥锁处理优先级反转的协议,RTAI Pthreads 中未实现。
    • protocol
      • PTHREAD_PRIO_NONE
      • PTHREAD_PRIO_INHERIT
      • PTHREAD_PRIO_PROTECT
    • 错误:
      • ENOSYS :不支持优先级调度。
      • EINVAL attr 不是互斥锁属性对象或 protocol 无效(仅设置时)。
      • ENOTSUP :不支持 protocol 值。

通过以上介绍,我们可以看到在嵌入式与实时应用的 Linux 编程中,这些模块和函数提供了丰富的功能,帮助开发者实现内核空间与用户空间的通信、共享内存管理以及多线程编程等任务。开发者在使用这些函数时,需要仔细处理可能出现的错误情况,以确保程序的稳定性和可靠性。

嵌入式与实时应用中的Linux编程接口详解(续)

7. 不同模块函数使用注意事项

为了更清晰地使用上述各个模块的函数,我们将不同模块常见函数的注意事项整理成如下表格:
| 模块 | 函数 | 注意事项 |
| — | — | — |
| rtai_fifos | rtf_create | fifo 必须小于 RTF_NO ,若 fifo 已存在,会根据需要调整大小 |
| rtai_fifos | rtf_destroy | 打开和关闭操作需成对,最后一次关闭才会真正销毁 FIFO |
| rtai_fifos | rtf_put 和 rtf_get | 仅实时任务可用,Linux 进程需通过 read() write() 操作对应设备 |
| rtai_shm | rtai_kmalloc 和 rtai_malloc | 首次调用分配空间,后续同名调用仅附加 |
| rtai_shm | rtai_kfree 和 rtai_free | 最后一个使用该区域的进程/任务调用才会释放内存 |
| rtai_lxrt | rt_task_init | stack_size max_msgsize 可为 0,使用默认值 |
| Posix 线程 | pthread_create | 确保 attr 有效,避免资源不足 |
| Posix 线程 | pthread_mutex_lock | 防止死锁,注意线程优先级与互斥锁优先级上限 |

8. 函数调用流程示例

下面以一个简单的多线程程序为例,展示如何综合使用上述函数进行多线程编程,同时使用互斥锁保护共享资源。

#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex;
int shared_variable = 0;

void *thread_function(void *arg) {
    pthread_mutex_lock(&mutex);
    shared_variable++;
    printf("Thread incremented shared_variable to %d\n", shared_variable);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_t thread;
    int result;

    // 初始化互斥锁
    result = pthread_mutex_init(&mutex, NULL);
    if (result != 0) {
        perror("pthread_mutex_init");
        return 1;
    }

    // 创建线程
    result = pthread_create(&thread, NULL, thread_function, NULL);
    if (result != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程结束
    result = pthread_join(thread, NULL);
    if (result != 0) {
        perror("pthread_join");
        return 1;
    }

    // 销毁互斥锁
    result = pthread_mutex_destroy(&mutex);
    if (result != 0) {
        perror("pthread_mutex_destroy");
        return 1;
    }

    return 0;
}

在这个示例中,我们创建了一个线程,该线程会对共享变量 shared_variable 进行加 1 操作。为了避免多个线程同时访问该变量导致的数据竞争问题,我们使用了互斥锁 pthread_mutex_t 来保护共享资源。

9. 错误处理策略

在使用上述函数时,错误处理是非常重要的。不同的错误类型需要不同的处理方式,以下是一些常见的错误处理策略:
- 资源不足错误 :如 EAGAIN ENOMEM 等,可尝试释放一些资源或等待一段时间后重试。
- 参数无效错误 :如 EINVAL ,检查传入的参数是否符合函数要求。
- 权限错误 :如 EPERM ,确保程序具有足够的权限执行相应操作。

以下是一个简单的错误处理示例:

#include <stdio.h>
#include <pthread.h>

int main() {
    pthread_t thread;
    pthread_attr_t attr;
    int result;

    // 初始化线程属性
    result = pthread_attr_init(&attr);
    if (result != 0) {
        if (result == ENOMEM) {
            fprintf(stderr, "Insufficient memory for thread attribute object\n");
        } else {
            fprintf(stderr, "Unknown error in pthread_attr_init: %d\n", result);
        }
        return 1;
    }

    // 创建线程
    result = pthread_create(&thread, &attr, NULL, NULL);
    if (result != 0) {
        if (result == EAGAIN) {
            fprintf(stderr, "Insufficient resources to create thread\n");
        } else if (result == EINVAL) {
            fprintf(stderr, "Invalid thread attributes\n");
        } else {
            fprintf(stderr, "Unknown error in pthread_create: %d\n", result);
        }
        pthread_attr_destroy(&attr);
        return 1;
    }

    // 销毁线程属性
    result = pthread_attr_destroy(&attr);
    if (result != 0) {
        if (result == EINVAL) {
            fprintf(stderr, "Invalid thread attribute object\n");
        } else {
            fprintf(stderr, "Unknown error in pthread_attr_destroy: %d\n", result);
        }
        return 1;
    }

    return 0;
}
10. 总结

通过对嵌入式与实时应用中的 Linux 编程接口的详细介绍,我们了解了多个重要模块的功能和使用方法。 rtai_fifos 模块实现了内核空间与用户空间的通信, rtai_shm 模块支持共享内存管理, rtai_lxrt 模块让用户空间进程能透明使用 RTAI API,而 Posix 线程 API 则提供了多线程编程的能力。

在实际开发中,开发者需要根据具体需求选择合适的模块和函数,同时要注意错误处理,确保程序的稳定性和可靠性。合理运用这些编程接口,可以高效地开发出满足嵌入式与实时应用需求的程序。

graph LR;
    A[开始] --> B[选择模块];
    B --> C{内核与用户通信?};
    C -- 是 --> D[使用 rtai_fifos];
    C -- 否 --> E{共享内存需求?};
    E -- 是 --> F[使用 rtai_shm];
    E -- 否 --> G{多线程编程?};
    G -- 是 --> H[使用 Posix 线程];
    G -- 否 --> I[其他操作];
    D --> J[编写代码];
    F --> J;
    H --> J;
    I --> J;
    J --> K[错误处理];
    K --> L[调试与优化];
    L --> M[结束];

这个流程图展示了在开发过程中选择合适模块的决策过程,以及后续的代码编写、错误处理和调试优化步骤。希望开发者能根据这个流程更好地运用这些编程接口,开发出高质量的嵌入式与实时应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值