[Linux] Simulate the sleep function

Get to know the sleep function first

#include <unistd.h>
unsigned int sleep(unsigned int seconds);
  • Function description: sleep() will suspend the current process until the time specified by the parameter seconds is reached, or it is interrupted by a signal.
  • Return value: If the process is suspended for the time specified by the parameter seconds, it will return 0, and if there is a signal interruption, it will return the remaining seconds.

Here is a brief introduction to several functions needed to simulate the implementation of the sleep function:
(1) sigaction: query or set the signal processing method
Function prototype:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

 struct sigaction {
     void     (*sa_handler)(int);
     void     (*sa_sigaction)(int, siginfo_t *, void *);
     sigset_t   sa_mask;
     int        sa_flags;
     void     (*sa_restorer)(void);
};

(2) alarm: Call alarm to set an alarm clock, and send a SIGALRM signal to the current process after zaiseconds. The default processing action of the signal terminates the current process.
Function prototype:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

(3) pause: Suspend the calling process until a signal is delivered.
Function prototype:

#include <unistd.h>
int pause(void);

Through the above functions, you can simulate the sleep function.

  1. First call sigaction to register the SIGALRM signal handler function sig_handler;
  2. Call alarm(seconds) to set the alarm clock;
  3. Call the pause process to hang and wait;
  4. After seconds seconds, the alarm clock times out, and the kernel sends SIGALRM to the process;
  5. Process pending signals before returning from kernel mode to user mode, and find that there is a SIGALRM signal, and its processing function is sig_handler;
  6. Switch to user mode to execute the capture function. When entering the sig_handler function, the SIGALRM signal will be automatically shielded. When returning from the sig_handler function, SIGALRM will be automatically unshielded, and then automatically execute sigreturn to enter the kernel again.
  7. Then return to user mode to continue executing the main control flow of the process;
  8. The pause function returns -1, calls alarm(0) to cancel the alarm, and calls sigaction to restore the previous processing action of the SIGALRM signal.
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sig_handler(int signo)
{
}
unsigned int mysleep(unsigned int seconds)
{
    struct sigaction new,old;
    unsigned int unslept = 0;
    new.sa_handler = sig_handler;
    sigemptyset(&new.sa_mask);
    new.sa_flags = 0;
    sigaction(SIGALRM, &new, &old);

    alarm(seconds);
    pause();
    unslept = alarm(0);
    sigaction(SIGALRM, &old, NULL);
    return unslept;
}
int main()
{
    while(1)
    {
        printf("Hello, world\n");
        mysleep(1);
    }
    return 0;
}

However, there is an error in such a process. It is assumed that after calling the alarm function in the second step, the kernel schedules a process with a higher priority to replace the execution of the current process, and there are many processes with a higher priority, each of which executes for a long time. time, so what will happen at this time?

  • After seconds seconds, the alarm clock times out, and the kernel sends a SIGALRM signal to the process, which is in a pending state;
  • After the process with higher priority is executed, the kernel schedules back to this process, the SIGALRM signal is delivered, and the processing function is executed and then enters the kernel again;
  • Return to the main control flow of this process, alarm returns, and call pause() to suspend and wait; this will always suspend and wait, which is inconsistent with our requirements

The root cause of this problem is that the timing of the system running is not the same as when we wrote the program. Since asynchronous events may occur in anything, if we do not think carefully when writing the program, there may be errors. This is called a race condition.
So how to solve it? Some students may have thought of this:

  1. Block the SIGALRM signal
  2. alarm(seconds)
  3. Unmask the SIGALRM signal
  4. pause()

There is a gap between unmasking the signal and calling pause, and SIGALRM may still be delivered in this gap; if we can combine the above three or four steps into one step, it can be an atomic operation, here the sigsuspend function is this function , let's take a look at this function first, and then transform mysleep.

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);

This function also did not return a value successfully. Only after a signal processing function is executed, sigsuspend returns. The return value is -1, and errno is set to EINTR. When calling this function, the signal mask word of the process is specified by the sigmask parameter. You can temporarily unmask a signal through sigmask, and then suspend and wait. When sigsuspend returns, the signal mask word of the process is restored to the original value. If It turned out that the signal was shielded, and it was still shielded after returning.

The following is our modified mysleep function

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sig_handler(int signo)
{
}
unsigned int mysleep(unsigned int seconds)
{   // 设定处理函数,捕捉信号
    struct sigaction new,old;
    unsigned int unslept = 0;
    new.sa_handler = sig_handler;
    sigemptyset(&new.sa_mask);
    new.sa_flags = 0;
    sigaction(SIGALRM, &new, &old);

    // 屏蔽SIGALRM信号
    sigset_t newmask, oldmask, suspmask;
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGALRM);
    sigprocmask(SIG_BLOCK, &newmask, &oldmask);//屏蔽SIGALRM

    // 设定闹钟
    alarm(seconds);

    // 调用sigsuspend临时解除屏蔽并挂起等待
    suspmask = oldmask;
    sigdelset(&suspmask, SIGALRM);
    sigsuspend(&suspmask);//解除对SIGALRM的屏蔽,然后挂起等待,SIGALRM递达后sigsuspend返回,
                          //自动恢复原来的屏蔽字,也就是在此屏蔽SIGALRM

    // 取消设定的闹钟,返回剩余的时间
    unslept = alarm(0);
    sigaction(SIGALRM, &old, NULL);
    // 再次解除对SIGALRM的屏蔽
    sigprocmask(SIG_UNBLOCK, &oldmask, NULL);
    return unslept;
}
int main()
{
    while(1)
    {
        printf("Hello, world\n");
        mysleep(1);
    }
    return 0;
}

volatile is a type modifier that guarantees that every access to a variable is from memory and not elsewhere; the volatile qualifier is necessary for situations where multiple execution flows in a program access the same global variable.


Reentrant function: a function is called by a different control flow, and it is possible to enter the function again before the first call has returned, which is called reentrancy; if a function is confused due to rushing, such a function It is called a non-reentrant function; conversely, if a function only accesses its own local variables or parameters, it is called a reentrant function.
Functions that meet the following conditions are not reentrant:

  • Call malloc or free, because malloc also uses the global linked list to manage the heap.
  • Standard I/O library functions are called. Many implementations of the standard I/O library use global data structures in a non-reentrant way.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325627206&siteId=291194637