1

我在 Windows 上做了很多编程,但现在我必须编写我的第一个 Linux 应用程序。

我需要使用 UDP 与硬件设备通信。我必须每秒发送 60 个数据包,大小为 40 字节。如果我在 1 秒内发送的数据包少于 60 个,就会发生不好的事情。数据包的数据可能需要一段时间才能生成。但是,如果数据还没有准备好通过网络发送出去,那么可以发送上次发送的相同数据。计算机是仅命令行安装程序,将仅运行此程序。

我对 Linux 了解不多,所以我希望大致了解如何设置应用程序来满足这些要求。我希望得到这样的答案:

制作2个线程,一个用于发送数据包,另一个用于计算。

但我不确定它是否那么简单(也许是这样)。也许制作某种守护进程会更可靠,它只是从共享内存或其他东西发送数据包,然后让另一个应用程序进行计算?如果是多进程解决方案,您会推荐什么通信机制?有什么方法可以让我的应用程序比正常或类似的东西更优先?

PS:越防弹越好!

4

10 回答 10

3

我做过一个类似的项目:嵌入式 Linux 计算机上的一个简单软件,以常规速度发送 CAN 消息。

我会选择两个线程的方法。给发送线程稍高的优先级,如果另一个线程计算这些块的速度很慢,则让它再次发送相同的数据块。

在大多数系统(包括嵌入式系统)上,每秒 60 个 UDP 数据包非常轻松,因此我不会花太多精力来优化线程之间的数据共享和数据包的发送。

事实上,我想说:保持简单!我确实是系统中唯一的应用程序,并且您对该系统具有合理的控制权,您无法从复杂的 IPC 方案和其他技巧中获得任何收益。保持简单将帮助您在更短的时间内以更少的缺陷和更少的时间生成更好的代码,这实际上意味着更多的测试时间。

于 2008-12-23T05:15:40.870 回答
1

您建议的两个线程将起作用。如果您在它们之间有一个 pipe(),那么您的计算线程可以在生成数据包时提供数据包,而您的通信线程使用 select() 来查看是否有任何新数据。如果没有,那么它只是从它的缓存中发送最后一个。

我可能已经过度简化了这个问题......

于 2008-12-23T05:01:55.030 回答
1

只要执行计算的负担不太大,使用一对线程的建议听起来就可以解决问题。

pipe()我倾向于使用互斥锁来锁定一块用于包含计算线程输出的内存,而不是使用Cogsy 建议的 —— 将其用作线程之间的传输区域。

当您的计算线程准备好输出到缓冲区时,它将获取互斥体,写入传输缓冲区并释放互斥体。

当您的传输线程准备好发送数据包时,它会“尝试”锁定互斥锁。如果它获得锁,则获取传输缓冲区的副本并发送它。如果它没有得到锁,发送最后一个副本。

您可以通过使用“nice”并指定负调整数字来控制流程的优先级以赋予它更高的优先级。请注意,您需要以超级用户身份(root 用户或使用“sudo”)执行此操作才能指定负值。


编辑:忘记添加 -是关于 Linux 上 pthreads 的一个很好的教程。还描述了互斥锁的使用。

于 2008-12-23T05:19:12.843 回答
1

我不太明白你的 60 个数据包/秒的要求有多难。每秒 60 个数据包的突发是否满足要求?或者每个数据包之间是否需要一个尖锐的 1/60 秒间隔?

这可能有点跑题了,但另一个重要的问题是如何配置 Linux 机器。我自己会使用实时Linux 内核并禁用所有不需要的服务。否则,无论您选择哪种架构,您的应用程序都会在某个时候丢失数据包,这是一个真正的风险。

无论如何,两个线程应该可以正常工作。

于 2008-12-23T05:48:04.827 回答
0

我发布了这个答案来说明一种与“显而易见”的方法完全不同的方法,希望有人发现它正是他们所需要的。没想到竟然被选为最佳答案!谨慎对待这个解决方案,因为存在潜在的危险和并发问题......

您可以使用setitimer()系统调用在指定的毫秒数后将 SIGALRM(警报信号)发送到您的程序。信号是异步事件(有点像消息),它会中断正在执行的程序以让信号处理程序运行。

当您的程序开始时,操作系统会安装一组默认信号处理程序,但您可以使用sigaction()安装自定义信号处理程序。

所以你只需要一个线程;使用全局变量,以便信号处理程序可以访问必要的信息并发送新数据包或根据需要重复上一个数据包。

这是一个对您有利的示例:

#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
int ticker = 0;

void timerTick(int dummy)
{
    printf("The value of ticker is: %d\n", ticker);
}

int main()
{
    int i;

    struct sigaction action;
    struct itimerval time;

    //Here is where we specify the SIGALRM handler
    action.sa_handler = &timerTick;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;

    //Register the handler for SIGALRM
    sigaction(SIGALRM, &action, NULL);

    time.it_interval.tv_sec = 1;       //Timing interval in seconds
    time.it_interval.tv_usec = 000000; //and microseconds
    time.it_value.tv_sec = 0;  //Initial timer value in seconds
    time.it_value.tv_usec = 1; //and microseconds

    //Set off the timer
    setitimer(ITIMER_REAL, &time, NULL);

    //Be busy
    while(1)
        for(ticker = 0; ticker < 1000; ticker++)
            for(i = 0; i < 60000000; i++)
                ;
}
于 2008-12-23T06:32:15.487 回答
0

两个线程都可以工作,你需要确保你锁定你的共享数据结构,这样发送线程在更新过程中看不到它。

每秒 60 次听起来并不难。

如果您真的关心调度,请将发送线程的调度策略设置为 SCHED_FIFO 和 mlockall() 其内存。这样,没有什么可以阻止它发送数据包(尽管如果同时在线上发送其他东西,它们仍然可能会迟到)

设备必须有一定的容差——每秒 60 个数据包是可以的,但设备的容差是多少?每秒20个?如果设备在没有收到的情况下会出现故障,我会以所需速率的三倍发送它们。

于 2008-12-23T13:07:12.540 回答
0

我会远离线程并使用进程和(也许)信号和文件。由于您说如果您不发送可能会发生“坏事”,因此您需要避免锁定和竞争条件。使用单独的进程和保存到文件的数据更容易做到这一点。

类似于一个进程将数据保存到文件,然后重命名并重新开始。而另一个进程拾取当前文件并每秒发送一次其内容。

与 Windows 不同,您可以在文件打开时复制(移动)文件。

于 2008-12-23T13:37:17.657 回答
0

遵循长期的 Unix 最佳实践:保持简单和模块化,解耦操作,让操作系统为您做尽可能多的工作。

这里的许多答案都在正确的轨道上,但我认为它们可以更简单:

  • 使用两个独立的进程,一个创建数据并将其写入标准输出,一个从标准输入读取数据并发送。让基本 I/O 库处理进程之间的数据流缓冲,让操作系统处理线程管理。

  • 首先使用计时器循环和虚假数据缓冲区构建基本发送器,并使其以正确的频率发送到设备。

  • 接下来让发件人从标准输入读取数据 - 您可以从文件重定向数据,例如“sender < textdata”

  • 接下来构建数据生产者并将其输出通过管道传递给发送者,例如“生产者|发送者”。

现在,您可以根据需要创建新的生产者,而不会干扰发送方。这个答案假设单向通信。

让答案尽可能简单会让你获得更大的成功,特别是如果你对基于 Linux/Unix 的系统还不是很流利的话。这是学习新系统的好机会,但不要过度使用。当工具可用时,很容易跳到复杂的答案,但是当简单的镘刀足够时,为什么还要使用推土机。互斥体、信号量、共享内存等都是有用且可用的,但会增加您可能并不真正需要的复杂性。

于 2008-12-28T02:24:55.937 回答
0

我同意两个线程的方法。我还会有两个静态缓冲区和一个共享枚举。发送线程应该有这个逻辑。

loop
    wait for timer
    grab mutex
    check enum {0, 1}
    send buffer 0 or 1 based on enum
    release mutex
end loop

另一个线程将具有以下逻辑:

loop
    check enum
    choose buffer 1 or 0 based on enum (opposite of other thread)
    generate data
    grab mutex
    flip enum
    release mutex
end loop

这样,发送方在发送数据的整个过程中始终拥有一个有效的缓冲区。只有生成器线程可以更改缓冲区指针,并且只有在发送未进行时才能这样做。此外,枚举翻转不应该花费太多的周期,以至于将更高优先级的发送者线程延迟很长时间。

于 2008-12-28T03:11:28.773 回答
0

谢谢大家,我会使用大家的建议。我希望我可以选择超过 1 个的答案!

对于那些好奇的人。我没有该设备的来源,它是一个专有的锁定系统。我还没有做足够的测试来看看每秒 60 个数据包有多挑剔。这就是他们所有有限的文档所说的“每秒 60 个数据包”。但是,由于设备的性质,数据包的爆发将是一件坏事。我想我可以通过每秒发送超过 60 个来弥补偶尔丢失的数据包。

于 2009-01-08T10:30:10.713 回答