15

在传统的嵌入式编程中,我们将给出一个延迟函数,如下所示:

for(i=0;i<255;i++)
   for(j=0;j<255;j++);

在微处理器看来,这就是 sleep() 函数的工作方式吗?

C 中的 sleep() 函数是否有替代方法?

4

16 回答 16

28

您描述的那种循环称为“忙等待”。在实际操作系统中,休眠不会导致忙等待;它告诉操作系统在睡眠期结束之前不要安排进程。

于 2008-11-05T04:36:08.590 回答
25

一种常见的机制是使用select()保证超时的 a,并将睡眠时间指定为超时:

// Sleep for 1.5 sec
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 500000;
select(0, NULL, NULL, NULL, &tv);

通常select()用于检查一组文件描述符并等待至少一个准备好执行 I/O。如果没有准备好(或者,在这种情况下,如果没有指定 fds),它将超时。

超过繁忙循环的优点select()是它在睡眠时消耗的资源非常少,而繁忙循环在其优先级允许的范围内独占处理器。

于 2008-11-05T04:58:19.667 回答
12

替代方案取决于您要做什么以及您使用的操作系统。

如果您只是想浪费时间,那么这些可能会有所帮助:

在大多数 unix 类型的系统上,您会发现一个“睡眠”功能,它或多或少类似于具有更高分辨率的睡眠。小心那个,因为它通常不能只睡一微秒。

在某些 unix 类型的系统上,select 系统调用可以与所有文件描述符设置为零一起使用,以获得相当准确的亚秒级等待。

在 Windows 系统上,您有睡眠,这几乎相同,但需要几毫秒。

在多任务操作系统中,有时可以将睡眠函数作为参数指定为 0。这通常会导致函数放弃它的时间片,但如果没有其他任务准备好运行,则会立即重新调度。

于 2008-11-05T04:45:08.743 回答
12

您不会使用您发布的代码在嵌入式系统上休眠。一个像样的编译器会完全删除它,即使你的编译器没有删除它也是次优的,因为在一个紧密的循环中运行处理器会消耗功率,这对于嵌入式系统来说是一个问题。即使不依靠电池运行的系统也关心电力使用,因为较低的电力使用意味着更便宜的电源和冷却。

您通常执行此操作的方式是您的 CPU 将执行某种 IDLE 或 SLEEP 指令,这将导致它暂时停止处理命令。连接到定时器电路的外部中断线将定期唤醒处理器,CPU 会在该点检查它是否已经睡眠足够长的时间,如果没有,它会重新进入睡眠状态。

//Pseudo code
int start = getTime();
int end = start + sleepTime;

while (getTime() < end) {
       asm("SLEEP");
}

确切的细节因处理器而异。如果您在操作系统上作为进程运行,则 sleep 调用通常只是告诉调度程序暂停您的进程,然后内核决定是调度另一个进程还是使 CPU 休眠。此外,上面的代码不适用于需要截止日期保证等的实时系统。在这些情况下,您需要获取循环中的时间,知道时间中断的持续时间,以便您知道是否可以重新睡眠超过最后期限,并可能重新编程计时器硬件或忙于等待。

于 2008-11-05T07:00:07.830 回答
7

您在 OP 中谈论“嵌入式编程”。如果您正在进行嵌入式工作并需要 sleep() 之类的东西,通常有可用的硬件计数器/计时器。这将因架构而异,因此请查看数据表。

如果你不做嵌入式工作,我很抱歉:)

于 2008-11-05T04:41:47.190 回答
6

如果您使用 for 循环,您最好知道它们编译成什么以及这些指令在您给定的时钟速度下需要多长时间,确保 CPU 运行您的指令而不是其他任何东西(这可以在嵌入式系统中完成,但它是棘手,因为它不允许中断)。

否则,您将无法判断实际需要多长时间。

早期的 PC 游戏存在这个问题——它们是为 4.7MHz 的 PC 构建的,当更快的计算机出现时,它们就无法玩了。

“睡眠”工作的最佳方式是让 CPU 知道它在任何给定点的时间。不一定是实际时间(上午 7 点 15 分),但至少是相对时间(自某个时间点以来的 8612 秒)。

这样,它可以将增量应用于当前时间并循环等待,直到达到当前+增量。

任何依赖于 CPU 周期数的东西本质上都是不可靠的,因为 CPU 可能会执行另一个任务并让你的循环挂起。

假设您有一个内存映射的 16 位 I/O 端口,CPU 每秒递增一次。我们还假设它位于嵌入式系统中的内存位置 0x33,其中 int 也是 16 位。一个叫做 sleep 的函数就变成了这样:

void sleep (unsigned int delay) {
    unsigned int target = peek(0x33) + delay;
    while (peek(0x33) != target);
}

您必须确保 peek() 每次都返回内存内容(因此优化编译器不会弄乱逻辑)并且您的 while 语句每秒运行一次以上,这样您就不会错过目标,但是这些是不影响我提出的概念的操作问题。

于 2008-11-05T04:43:01.477 回答
6

这里有更多关于 sleep() 如何工作的信息

顺便说一句,忙碌的等待不一定适合业余爱好者——尽管它确实会烧毁您可能想要用于其他目的的处理器。如果您使用的是时间源,您将受限于该源的粒度。例如,如果您有一个 1 ms 的计时器,并且想要 500 uS,那么您就有问题了。如果您的嵌入式系统可以处理您将在 500 uSec 的循环中嗡嗡作响的事实,那可能是可以接受的。即使您有一个具有所需粒度的计时器,您也需要在正确的时间从该计时器中获得一个中断......然后调度中断处理程序......然后进入您的代码。有时,繁忙的循环是最方便的解决方案。 有时。

于 2008-11-05T05:44:42.553 回答
5

Busy-waiting 即使在嵌入式系统中也适用于业余爱好者,使用实时源。

于 2008-11-05T05:43:55.253 回答
5

任何体面的 C 编译器都无需额外的工作即可完全删除您的代码,延迟就会消失

于 2008-11-05T06:27:49.940 回答
3

sleep 实际上与操作系统接口,其中睡眠进程被放置在调度队列之外。我通常使用:

poll(0, 0, milliseconds);

适用于 POSIX 兼容系统。select也适用于 Windows(它们必须有一个本机 API(可能称为Sleep)。)

于 2008-11-05T05:55:46.540 回答
2

Joseph Albahari 在“C# 中的线程”中的第 20 页对此进行了有趣的讨论。您在 .Net 中的睡眠时间不能少于 1 毫秒,但 DateTime.Ticks 的间隔粒度为 100 纳秒(= 0.1 微秒)。为了控制我的 5 轴 CNC 步进器,我只需要在步进命令之间暂停 10 微秒。我已经使用微控制器进行了令人讨厌的循环,但我认为如果你有一大堆工作,交出一个处理器来完成这项工作是可以的,尽可能停止线程。至少它不会总是一样的。

于 2011-11-08T04:16:41.700 回答
2
#include <Windows.h>

static NTSTATUS(__stdcall *NtDelayExecution)(BOOL Alertable, PLARGE_INTEGER DelayInterval) = (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");

static NTSTATUS(__stdcall *ZwSetTimerResolution)(IN ULONG RequestedResolution, IN BOOLEAN Set, OUT PULONG ActualResolution) = (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwSetTimerResolution");




static void SleepShort(float milliseconds) {
    static bool once = true;
    if (once) {
        ULONG actualResolution;
        ZwSetTimerResolution(1, true, &actualResolution);
        once = false;
    }

    LARGE_INTEGER interval;
    interval.QuadPart = -1 * (int)(milliseconds * 10000.0f);
    NtDelayExecution(false, &interval);
}
于 2015-07-14T16:01:39.073 回答
2

在 linux 中可用 usleep( int microseconds ) nanosleep( ... ) 更精确的请参见调用参数的手册页

于 2016-07-30T21:56:59.730 回答
1

在 unix 衍生操作系统中,您可能会安排一个 signal() 调用,并且您的代码会简单地阻塞代码,直到发出信号。信号是为此目的而设计的,它们非常简单有效。

于 2008-11-05T05:39:10.740 回答
0

一个尝试......真正解决这个问题,即有效的东西(不像上面的回答尝试)大声笑

我仍然需要改进这段代码才能让它出来。很少有附加组件是受欢迎的。

// Sleep for both Windows and Linux: 
// Too bad? No one proposed you a solution that works? 
// Since Windows has no select.h nor poll.h, an implementation
// is necessary.
//  
// Solutions on boards are often refered to use either select or poll, but ok, what about C (not c++)?
//
/// implementation of poll is destined in this attempt for windows
/// Ideally, you add this part of code to the header of you *.c file, and it might work through...

#ifdef WIN32
#include <time.h>
#include <sys/time.h>
#include <ws2tcpip.h>
#include <Winsock2.h>
#include <windows.h>
/* winsock doesn't feature poll(), so there is a version implemented
 * in terms of select() in mingw.c. The following definitions
 * are copied from linux man pages. A poll() macro is defined to
 * call the version in mingw.c.
 */
#define POLLIN      0x0001    /* There is data to read */
#define POLLPRI     0x0002    /* There is urgent data to read */
#define POLLOUT     0x0004    /* Writing now will not block */
#define POLLERR     0x0008    /* Error condition */
#define POLLHUP     0x0010    /* Hung up */
#define POLLNVAL    0x0020    /* Invalid request: fd not open */
struct pollfd {
  SOCKET fd;        /* file descriptor */
  short events;     /* requested events */
  short revents;    /* returned events */
};

int mingw_poll (struct pollfd *, unsigned int, int);

#define poll(x, y, z)        mingw_poll(x, y, z)
#endif





int mingw_poll(struct pollfd *fds, unsigned int nfds, int timo)
{
    struct timeval timeout, *toptr;
    fd_set ifds, ofds, efds, *ip, *op;
    int i, rc;

    /* Set up the file-descriptor sets in ifds, ofds and efds. */
    FD_ZERO(&ifds);
    FD_ZERO(&ofds);
    FD_ZERO(&efds);
    for (i = 0, op = ip = 0; i < nfds; ++i) {
    fds[i].revents = 0;
    if(fds[i].events & (POLLIN|POLLPRI)) {
        ip = &ifds;
        FD_SET(fds[i].fd, ip);
    }
    if(fds[i].events & POLLOUT) {
        op = &ofds;
        FD_SET(fds[i].fd, op);
    }
    FD_SET(fds[i].fd, &efds);
    } 

    /* Set up the timeval structure for the timeout parameter */
    if(timo < 0) {
    toptr = 0;
    } else {
    toptr = &timeout;
    timeout.tv_sec = timo / 1000;
    timeout.tv_usec = (timo - timeout.tv_sec * 1000) * 1000;
    }

#ifdef DEBUG_POLL
    printf("Entering select() sec=%ld usec=%ld ip=%lx op=%lx\n",
           (long)timeout.tv_sec, (long)timeout.tv_usec, (long)ip, (long)op);
#endif
    rc = select(0, ip, op, &efds, toptr);
#ifdef DEBUG_POLL
    printf("Exiting select rc=%d\n", rc);
#endif

    if(rc <= 0)
    return rc;

    if(rc > 0) {
        for (i = 0; i < nfds; ++i) {
            int fd = fds[i].fd;
        if(fds[i].events & (POLLIN|POLLPRI) && FD_ISSET(fd, &ifds))
            fds[i].revents |= POLLIN;
        if(fds[i].events & POLLOUT && FD_ISSET(fd, &ofds))
            fds[i].revents |= POLLOUT;
        if(FD_ISSET(fd, &efds))
            /* Some error was detected ... should be some way to know. */
            fds[i].revents |= POLLHUP;
#ifdef DEBUG_POLL
        printf("%d %d %d revent = %x\n", 
                FD_ISSET(fd, &ifds), FD_ISSET(fd, &ofds), FD_ISSET(fd, &efds), 
                fds[i].revents
        );
#endif
        }
    }
    return rc;
}
于 2013-02-06T10:13:25.827 回答
0

我在这篇文章(http://cboard.cprogramming.com/c-programming/111229-how-use-sleep-function.html)中找到了这个函数,它可以工作:

#include <stdio.h>
#include <windows.h>

int main()
{
    puts("Hello \n");
    /* in windows.h is declared the Sleep (upper S) function and it takes time in 
miliseconds */
    Sleep(3000);
    puts("World \n");
    return 0;
}
于 2015-02-06T16:43:12.587 回答