UNIX-Linux环境编程(六):信号处理

STM32输入捕获中断函数逻辑理解 STM32输入捕获中断函数逻辑理解 写在前面: 本人最近在初学stm32定时器的应用之输入捕获的时候,发现其中断函数处理上升沿和下降沿的逻辑比较难理解,于是花了一些时间写下理解的过程。若有不正确之处,望请指正!!! 1. 首先展示TIM 中断函数中的全部代码(注释自己添加的): void TIM3_IRQHandler(void) { if(TIM3CH1_CAPTURE_STA&0X80==0) //表示未捕获完成 { if(TIM_GetITStatus(TIM3,TIM_ 阅读详情

一、基本概念

1. 中断

中止(注意不是终止)当前正在执行的程序,转而执行其它任务。
硬件中断:来自硬件设备的中断。
软件中断:来自其它程序的中断。

2. 信号是一种软件中断

信号提供了一种以异步方式执行任务的机制。

3. 常见信号
信号解释产生条件默认动作
SIGHUP(1)连接断开信号如果终端接口检测一个连接断开,则将此信号发送给与该终端相关的控制进程(会话首进程)终止
SIGINT(2)终端中断符信号用户按中断键(Ctrl+C),产生此信号,并送至前台进程组的所有进程终止
SIGQUIT(3)终端退出符信号用户按退出键(Ctrl+),产生此信号,并送至前台进程组的所有进程终止+core
SIGILL(4)非法硬件指令信号进程执行了一条非法硬件指令终止+core
SIGTRAP(5)硬件故障信号指示一个实现定义的硬件故障。常用于调试终止+core
SIGABRT(6)异常终止信号调用abort函数,产生此信号终止+core
SIGBUS(7)总线错误信号指示一个实现定义的硬件故障,常用于内存故障终止+core
SIGFPE(8)算术异常信号 表表示一个算术运算异常,例如除以0、浮点溢出等终止+core
SIGKILL(9)终止信号不能被捕获或忽略。常用于杀死进程终止
SIGUSR1(10)用户定义信号用户定义信号,用于应用程序终止
SIGSEGV(11)段错误信号试图访问未分配的内存,或向没有写权限的内存写入数据终止+core
SIGUSR2(12)用户定义信号 用用户定义信号,用于应用程序终止
SIGPIPE(13)管道异常信号写管道时读进程已终止,或写SOCK_STREAM类型套接字时连接已断开,均产生此信号终止
SIGALRM(14)闹钟信号以alarm函数设置的计时器到期,或以setitimer函数设置的间隔时间到期,均产生此信号终止
SIGTERM(15)终止信号由kill命令发送的系统默认终止信号终止
SIGSTKFLT(16)数协器栈故障信号表示数学协处理器发生栈故障终止
SIGCHLD(17)子进程状态改变信号在一个进程终止或停止时,将此信号发送给其父进程忽略
SIGCONT(18)使停止的进程继续向处于停止状态的进程发送此信号,令其继续运行继续/忽略
SIGSTOP(19)停止信号不能被捕获或忽略。停止一个进程停止进程
SIGTSTP(20)终端停止符信号用户按停止键(Ctrl+Z),产生此信号,并送至前台进程组的所有进程停止进程
SIGTTIN(21)后台读控制终端信号后台进程组中的进程试图读其控制终端,产生此信号停止
SIGTTOU(22)后台写控制终端信号后台进程组中的进程试图写其控制终端,产生此信号停止
SIGURG(23)紧急情况信号有紧急情况发生,或从网络上接收到带外数据,产生此信号忽略
SIGXCPU(24)超过CPU限制信号进程超过了其软CPU时间限制,产生此信号终止+core
SIGXFSZ(25)超过文件长度限制信号进程超过了其软文件长度限制,产生此信号终止+core
SIGVTALRM(26)虚拟闹钟信号以setitimer函数设置的虚拟间隔时间到期,产生此信号终止
SIGPROF(27)虚拟梗概闹钟信号以setitimer函数设置的虚拟梗概统计间隔时间到期,产生此信号终止
SIGWINCH(28)终端窗口大小改变信号以ioctl函数更改窗口大小,产生此信号忽略
SIGIO(29)异步I/O信号指示一个异步I/O事件终止
SIGPWR(30)电源失效信号电源失效,产生此信号终止
SIGSYS(31)非法系统调用异常指示一个无效的系统调用终止+core
4. 不可靠信号(非实时信号)

•1) 那些建立在早期机制上的信号被称为不可靠信号,小于SIGRTMIN(34)的信号都是不可靠信号。
•2) 不支持排队,可能会丢失。同一个信号产生多次,进程可能只收到一次该信号。
•3) 进程每次处理完这些信号后,对相应信号的响应被自动恢复为默认动作,除非显示地通过signal函数重新设置一次信号处理程序。

5. 可靠信号(实时信号)

•1) 位于[SIGRTMIN(34),SIGRTMAX(64)]区间的信号都是可靠信号。
•2) 支持排队,不会丢失。
•3) 无论可靠信号还是不可靠信号,都可以通过sigqueue/sigaction函数发送/安装,以获得比其早期版本kill/signal函数更可靠的使用效果。

6. 信号的来源

•1) 硬件异常:除0、无效内存访问等。
这些异常通常被硬件(驱动)检测到,并通知系统内核。
系统内核再向引发这些异常的进程递送相应的信号。
•2) 软件异常:通过kill/raise/alarm/setitimer/sigqueue函数产生的信号。

7. 信号处理

•1) 忽略。
•2) 终止进程。
•3) 终止进程同时产生core文件。
•4) 捕获并处理。当信号发生时,内核会调用一个事先注册好的用户函数(信号处理函数)。
范例:loop.c

#include <stdio.h>
#include <unistd.h>

int main (void) 
{
    printf ("%u进程:我在运行,按<Ctrl+C>或<Ctrl+\\>终止...\n",getpid ());

    for (;;);

    return 0;
}

# a.out
按中断键(Ctrl+C),发送SIGINT(2)终端中断符信号。

# a.out
按退出键(Ctrl+\),发送SIGQUIT(3)终端退出符信号。

二、signal

#include <signal.h>

typedef void (*sighandler_t) (int);

sighandler_t signal (int signum,sighandler_t handler);

signum  - 信号码,也可使用系统预定义的常量宏,
	      如SIGINT等。

handler - 信号处理函数指针或以下常量:
		  SIG_IGN: 忽略该信号;
		  SIG_DFL: 默认处理。

成功返回原来的信号处理函数指针或SIG_IGN/SIG_DFL常量,
失败返回SIG_ERR。

•1) 在某些Unix系统上,通过signal函数注册的信号处理函数只一次有效。
即内核每次调用信号处理函数前,会将对该信号的处理自动恢复为默认方式。
为了获得持久有效的信号处理,可以在信号处理函数中再次调用signal函数,重新注册一次。
例如:

void sigint (int signum) 
{
  ...
 signal (SIGINT, sigint);
}

int main (void) 
{
	...
	signal (SIGINT, sigint);
	...
}

•2) SIGKILL/SIGSTOP信号不能被忽略,也不能被捕获。
•3) 普通用户只能给自己的进程发送信号,root用户可以给任何进程发送信号。
范例:signal.c

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigint (int signum) 
{
    printf ("%u进程:收到SIGINT信号!\n", getpid ());
    //  signal (SIGINT, SIG_DFL);
    //  signal (SIGINT, sigint);
}

void sigkill (int signum) 
{
    printf ("%u进程:收到SIGKILL信号!\n", getpid ());
}

int main () 
{
    /*
    if (signal (SIGINT, SIG_IGN) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }
    */
    if (signal (SIGINT, sigint) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }
    /*
    if (signal (SIGKILL, SIG_IGN) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
.    }

    if (signal (SIGKILL, sigkill) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }
    */
    printf ("%u进程:我在运行,按<Ctrl+C>没用,按<Ctrl+\\>终止..\n",getpid ());

    for (;;);

    return 0;
}

三、子进程的信号处理

1、子进程会继承父进程的信号处理方式,直到子进程调用exec函数。
范例:fork.c


#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigint (int signum) 
{
    printf ("%u进程:收到SIGINT信号!\n", getpid ());
}

int main () 
{
    if (signal (SIGINT, sigint) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }

    if (signal (SIGQUIT, SIG_IGN) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }

    pid_t pid = fork ();
    if (pid == -1) 
    {
        perror ("fork");
        return -1;
    }

    if (pid == 0) 
    {
        printf ("%u进程:我是子进程,正在运行...\n", getpid ());
        for (;;);
        return 0;
    }

    sleep (1);
    printf ("%u进程:我是父进程,即将退出。\n", getpid ());

    return 0;
}


2、进程调用exec函数后, exec函数将被父进程设置为捕获的信号恢复至默认处理, 其余保持不变。

四、发送信号

1. 键盘

Ctrl+C - SIGINT(2),终端中断
Ctrl+\ - SIGQUIT(3),终端退出
Ctrl+Z - SIGTSTP(20),终端暂停

2. 错误

除0 - SIGFPE(8),算术异常
非法内存访问 - SIGSEGV(11),段错误
硬件故障 - SIGBUS(7),总线错误

3. 命令

kill -信号 进程号

4. 函数
  1. kill

     #include <signal.h>
    
     int kill (pid_t pid, int sig);
    
     成功返回0,失败返回-1。
    
     pid >  0 - 向pid进程发送sig信号。
    
     pid =  0 - 向同进程组的所有进程发送信号。
    
     pid = -1 - 向所有进程发送信号,
     		   前提是调用进程有向其发送信号的权限。
    
     pid < -1 - 向绝对值等于pid的进程组发信号。
    

0信号为空信号。
若sig取0,则kill函数仍会执行错误检查,
但并不实际发送信号。这常被用来确定一个进程是否存在。
向一个不存在的进程发送信号,会返回-1,且errno为ESRCH。

范例:kill.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void sigint (int signum) 
{
    printf ("%u进程:收到SIGINT信号,即将退出。\n", getpid ());
    exit (0);
}

int main (void) 
{
    pid_t pid = fork ();
    if (pid == -1) 
    {
        perror ("fork");
        return -1;
    }

    if (pid == 0) 
    {
        printf ("%u进程:我是子进程,正在运行...\n", getpid ());

        if (signal (SIGINT, sigint) == SIG_ERR) 
        {
            perror ("signal");
            return -1;
        }

        for (;;);

        return 0;
    }

    sleep (1);

    printf ("%u进程:我是父进程,向%u进程发送SIGINT信号...\n",
        getpid (), pid);

    if (kill (pid, SIGINT) == -1) 
    {
        perror ("kill");
        return -1;
    }

    if ((pid = wait (0)) == -1) 
    {
        perror ("wait");
        return -1;
    }

    printf ("%u进程:%u进程已退出。\n", getpid (), pid);

    return 0;
}
  1. raise

     #include <signal.h>
    
     int raise (int sig);
    
     向调用进程自身发送sig信号。成功返回0,失败返回-1。
    

五、pause

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

1、使调用进程进入睡眠状态,直到有信号终止该进程或被捕获。
2、只有调用了信号处理函数并从中返回以后,该函数才会返回。
3、该函数要么不返回(未捕获到信号),要么返回-1(被信号中断),errno为EINTR。
4、相当于没有时间限制的sleep函数。
范例:pause.c

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void sigint (int signum) 
{
    printf ("%u进程:收到SIGINT信号!\n", getpid ());
}

int main () 
{
    pid_t pid = fork ();
    if (pid == -1) 
    {
        perror ("fork");
        return -1;
    }

    if (pid == 0) 
    {
        if (signal (SIGINT, sigint) == SIG_ERR) 
       {
            perror ("signal");
            return -1;
        }

        printf ("%u进程:我是子进程,大睡无期...\n", getpid ());
        pause ();
        printf ("%u进程:我被唤醒了,即将退出。\n", getpid ());

        return 0;
    }

    sleep (1);

    printf ("%u进程:我是父进程,向%u进程发送SIGINT信号...\n",
        getpid (), pid);

   if (kill (pid, SIGINT) == -1) 
    {
        perror ("kill");
        return -1;
    }

    if ((pid = wait (0)) == -1) 
    {
        perror ("wait");
        return -1;
    }

    printf ("%u进程:%u进程已退出。\n", getpid (), pid);

    return 0;
}

七、alarm

#include <unistd.h>

unsigned int alarm (unsigned int seconds);

1、使内核在seconds秒之后,向调用进程发送SIGALRM(14)闹钟信号。
范例:clock.c

#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>

void sigalrm (int signum) 
{
    time_t t = time (NULL);
    struct tm* lt = localtime (&t);

    printf ("\r%02d:%02d:%02d", lt->tm_hour, lt->tm_min, lt->tm_sec);

    alarm (1);
}

int main (void) 
{
    setbuf (stdout, NULL);

    if (signal (SIGALRM, sigalrm) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }

    sigalrm (SIGALRM);

    for (;;);

    return 0;
}

2、SIGALRM信号的默认处理是终止进程。
3、若之前已设过定时且尚未超时,则调用该函数会重新设置定时,并返回之前定时的剩余时间。
4、seconds取0表示取消之前设过且尚未超时的定时。
范例:alarm.c


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void sigalrm (int signum) 
{
    printf ("收到SIGALRM信号!即将退出。\n");
    exit (0);
}

int main () 
{
    if (signal (SIGALRM, sigalrm) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }

    unsigned int remain = alarm (10);
    printf ("定时10秒。\n");

    printf ("睡眠2秒...\n");
    sleep (2);
    /*
    remain = alarm (0);
    printf ("取消定时,剩余%u秒。\n", remain);
    */
    remain = alarm (5);
    printf ("定时5秒,剩余%u秒。\n", remain);

    for (;;);

    return 0;
}

八、信号集与信号阻塞(信号屏蔽)

1. 信号集

•1) 多个信号的集合类型:
sigset_t,128个二进制位
每个位代表一个信号。
•2) 相关函数

	#include <signal.h>

	// 将信号集set中的全部信号位置1
	int sigfillset (sigset_t* set);

	// 将信号集set中的全部信号位清0
	int sigemptyset (sigset_t* set);

	// 将信号集set中与signum对应的位置1
	int sigaddset (sigset_t* set, int signum);

	// 将信号集set中与signum对应的位清0
	int sigdelset (sigset_t* set, int signum);
	成功返回0,失败返回-1。

	// 判断信号集set中与signum对应的位是否为1
	int sigismember (const sigset_t* set, int signum);
	若信号集set中与signum对应的位为1,则返回1,否则返回0。
2. 信号屏蔽

•1) 当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程称为递送(delivery)。
•2) 信号从产生到完成递送之间存在一定的时间间隔。
处于这段时间间隔中的信号状态,称为未决(pending)。
•3) 每个进程都有一个信号掩码(signal mask)。
它实际上是一个信号集,其中包括了所有需要被屏蔽的信号。
•4) 可以通过sigprocmask函数,检测和修改调用进程的信号掩码。
也可以通过sigpending函数,获取调用进程当前处于未决状态的信号集。
•5) 当进程执行诸如更新数据库等敏感任务时,可能不希望被某些信号中断。
这时可以暂时屏蔽(注意不是忽略)这些信号,使其滞留在未决状态。
待任务完成以后,再回过头来处理这些信号。
•6) 在信号处理函数的执行过程中,这个正在被处理的信号总是处于信号掩码中。

	#include <signal.h>

	int sigprocmask (int how, const sigset_t* set,
sigset_t* oldset);

	成功返回0,失败返回-1。

	how  - 修改信号掩码的方式,可取以下值:

	    SIG_BLOCK: 新掩码是当前掩码和set的并集
	    (将set加入信号掩码);

	   SIG_UNBLOCK: 新掩码是当前掩码和set补集的交集
	   (从信号掩码中删除set);

	    SIG_SETMASK: 新掩码即set(将信号掩码设为set)。

	set  - NULL则忽略。

	oset - 备份以前的信号掩码,NULL则不备份。

	int sigpending (sigset_t* set);

	set - 输出,调用进程当前处于未决状态的信号集。

	成功返回0,失败返回-1。

注意:对于不可靠信号,通过sigprocmask函数设置信号掩码以后,
相同的被屏蔽信号只会屏蔽第一个,并在恢复信号掩码后被递送,其余的则直接忽略掉。
而对于可靠信号,则会在信号屏蔽时按其产生的先后顺序排队,一旦恢复信号掩码,这些信号会依次被信号处理函数处理。
范例:sigmask.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void sighand (int signum) 
{
    printf ("%u进程:收到%d信号!\n", getpid (), signum);
}

void updatedb () 
{
    int i;
    for (i = 0; i < 5; i++) 
    {
        printf ("%u进程:更新第%d条记录。\n", getpid (), i + 1);
        sleep (1);
    }
}

int main () 
{
    if (signal (SIGINT, sighand) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }

    if (signal (50, sighand) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }

    printf ("%u进程:屏蔽%d信号和%d信号。\n", getpid (), SIGINT, 50);

    sigset_t new;

    if (sigemptyset (&new) == -1) 
    {
        perror ("sigemptyset");
        return -1;
    }

    if (sigaddset (&new, SIGINT) == -1) 
    {
        perror ("sigaddset");
        return -1;
    }

    if (sigaddset (&new, 50) == -1) 
    {
        perror ("sigaddset");
        return -1;
    }

    sigset_t old;

    if (sigprocmask (SIG_SETMASK, &new, &old) == -1) 
    {
        perror ("sigprocmask");
        return -1;
    }

    pid_t pid = fork ();
    if (pid == -1) 
   {
        perror ("fork");
        return -1;
    }

    if (pid == 0) 
    {
       pid_t ppid = getppid ();

        int i;
        for (i = 0; i < 5; i++) 
        {
            printf ("%u进程:向%u进程发送%d信号...\n", getpid (),
                ppid, 50);
            kill (ppid, 50);
        }

        return 0;
    }

    updatedb ();

    sigset_t pend;

    if (sigpending (&pend) == -1) 
    {
        perror ("sigpending");
        return -1;
    }

    if (sigismember (&pend, SIGINT))
        printf ("%u进程:发现%d信号未决。\n", getpid (), SIGINT);

    if (sigismember (&pend, 50))
        printf ("%u进程:发现%d信号未决。\n", getpid (), 50);

    printf ("%u进程:取消对%d信号和%d信号的屏蔽。\n", getpid (),
        SIGINT, 50);

    if (sigprocmask (SIG_SETMASK, &old, NULL) == -1) 
    {
       perror ("sigprocmask");
        return -1;
   }

   if ((pid = fork ()) == -1) 
    {
        perror ("fork");
        return -1;
    }

    if (pid == 0) 
    {
        pid_t ppid = getppid ();

        int i;
        for (i = 0; i < 5; i++) 
        {
            printf ("%u进程:向%u进程发送%d信号...\n", getpid (),
                ppid, 50);
            kill (ppid, 50);
        }

        return 0;
    }

    updatedb ();

    return 0;
}

九、sigaction

#include <signal.h>

int sigaction (
    int                     signum, // 信号码
    const struct sigaction* act,    // 信号处理方式
    struct sigaction*       oldact  // 原信号处理方式
                                    // (可为NULL)
);

struct sigaction {
    void     (*sa_handler) (int);
                                // 信号处理函数指针1
    void     (*sa_sigaction) (int, siginfo_t*, void*);
                                // 信号处理函数指针2
    sigset_t sa_mask;           // 信号掩码
    int      sa_flags;          // 信号处理标志
   void     (*sa_restorer)(void);
                                // 保留,NULL
};

成功返回0,失败返回-1。

typedef struct siginfo {
	pid_t    si_pid;   // 发送信号的PID
	sigval_t si_value; // 信号附加值(需要配合sigqueue函数)
	...
}   siginfo_t;

typedef union sigval {
	int   sival_int;
	void* sival_ptr;
}   sigval_t;

•1) 缺省情况下,在信号处理函数的执行过程中,会自动屏蔽这个正在被处理的信号。
而对于其它信号则不会屏蔽,通过sigaction::sa_mask成员可以人为指定。
在信号处理函数的执行过程中,需要加入进程信号掩码中的信号。
并在信号处理函数执行完之后,自动解除对这些信号的屏蔽。
•2) sigaction::sa_flags可为以下值的位或: ◦SA_ONESHOT/SA_RESETHAND
执行完一次信号处理函数后,即将对此信号的处理恢复为
默认方式(这也是老版本signal函数的缺省行为。
◦SA_NODEFER/SA_NOMASK
在信号处理函数的执行过程中,不屏蔽这个正在被处理的信号。
◦SA_NOCLDSTOP
若signum参数取SIGCHLD,则当子进程暂停时,不通知父进程。
◦SA_RESTART
系统调用一旦被signum参数所表示的信号中断,会自行重启。
◦SA_SIGINFO
使用信号处理函数指针2,通过该函数的第二个参数,提供更多信息。
范例:sigact.c

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigint1 (int signum) 
{
    printf ("收到SIGINT信号!睡眠中...\n");
    sleep (5);
    printf ("睡醒了。\n");
}

void sigint2 (int signum, siginfo_t* si, void* pv) 
{
    printf ("收到发自%u进程的SIGINT信号!\n", si -> si_pid);
}

int main () 
{
    printf ("按<Ctrl+C>和<Ctrl+\\>看效果...\n");

    struct sigaction act = {};

    printf ("睡眠中只屏蔽SIGINT,不屏蔽SIGQUIT。\n");
    act.sa_handler = sigint1;
    /*
    printf ("睡眠中既屏蔽SIGINT,也屏蔽SIGQUIT。\n");
    act.sa_handler = sigint1;
    sigemptyset (&act.sa_mask);
    sigaddset (&act.sa_mask, SIGQUIT);
    *//*
    printf ("睡眠中既不屏蔽SIGINT,也不屏蔽SIGQUIT。\n");
    act.sa_handler = sigint1;
    act.sa_flags = SA_NOMASK;
    *//*
    printf ("睡眠中不屏蔽SIGINT,只屏蔽SIGQUIT。\n");
    act.sa_handler = sigint1;
    sigemptyset (&act.sa_mask);
    sigaddset (&act.sa_mask, SIGQUIT);
    act.sa_flags = SA_NOMASK;
    *//*
    printf ("附带更多信号信息。\n");
    act.sa_sigaction = sigint2;
    act.sa_flags = SA_SIGINFO;
    *//*
    printf ("一次性信号处理。\n");
    act.sa_handler = sigint1;
    act.sa_flags = SA_RESETHAND;
    */
    if (sigaction (SIGINT, &act, NULL) == -1)
    {
        perror ("sigaction");
        return -1;
   }

    for (;;) pause ();

    return 0;
}

十、sigqueue

#include <signal.h>

int sigqueue (pid_t pid, int sig,const union sigval value);

向pid进程发送sig信号,附加value值(整数或指针)。
成功返回0,失败返回-1。

注意:sigqueue函数对不可靠信号不做排队,会丢失信号。

十一、计时器

  1. 系统为每个进程维护三个计时器
    •1) 真实计时器:
    程序运行的实际时间。
    •2) 虚拟计时器:
    程序运行在用户态所消耗的时间。
    •3) 实用计时器:
    程序运行在用户态和内核态所消耗的时间之和。
    实际时间(真实计时器) = 实用计时器(用户时间(虚拟计时器) + 内核时间)+ 睡眠时间

  2. 为进程设定计时器
    •1) 用指定的初始间隔和重复间隔为进程设定好计时器后,该计时器就会定时地向进程发送时钟信号。
    •2) 三个计时器所发送的时钟信号分别为:

     SIGALRM - 真实计时器 
     SIGVTALRM - 虚拟计时器 
     SIGPROF - 实用计时器
    

•3) 获取/设置计时器
#include <sys/time.h>

int getitimer (int which,
   struct itimerval* curr_value);

获取计时器设置。成功返回0,失败返回-1。

int setitimer (int which,
   const struct itimerval* new_value,
   struct itimerval* old_value);

设置计时器。成功返回0,失败返回-1。

which      - 指定哪个计时器,取值:

   ITIMER_REAL: 真实计时器;
   ITIMER_VIRTUAL: 虚拟计时器;
   ITIMER_PROF: 实用计时器。

curr_value - 当前设置。

new_value  - 新的设置。

old_value  - 旧的设置(可为NULL)。

struct itimerval {
   struct timeval it_interval;
   // 重复间隔(每两个时钟信号的时间间隔),
  // 取0将使计时器在发送第一个信号后停止
 	 struct timeval it_value;
  // 初始间隔(从调用setitimer函数到第一次发送
  // 时钟信号的时间间隔),取0将立即停止计时器
.};

	struct timeval {
    long tv_sec;  // 秒数
    long tv_usec; // 微秒数
};

范例:timer.c

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

int g_hour = 0, g_min = 0, g_sec = 0, g_msec = 0;

void sigalrm (int signum) 
{
    printf ("\r%02d:%02d:%02d.%03d", g_hour, g_min, g_sec, g_msec);

    if (++g_msec == 1000) 
    {
        g_msec = 0;
        if (++g_sec == 60) 
        {
            g_sec = 0;
            if (++g_min == 60) 
            {
                g_min = 0;
                if (++g_hour == 24)
                    g_hour = 0;
            }
        }
    }
}

void sigint (int signum) 
{
    static int run = 1;
    struct itimerval it = {{0, 1000},{0, 0}};

   if (run)
        setitimer (ITIMER_REAL, &it, NULL);
    else 
    {
        printf ("\n");
        g_hour = g_min = g_sec = g_msec = 0;
        it.it_value.tv_usec = 1;
        setitimer (ITIMER_REAL, &it, NULL);
    }

    run ^= 1;
}

int main () 
{
    setbuf (stdout, NULL);

    if (signal (SIGALRM, sigalrm) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }

    if (signal (SIGINT, sigint) == SIG_ERR) 
    {
        perror ("signal");
        return -1;
    }

    struct itimerval it = {{0, 1000},{5, 0}};

    printf ("%ld秒钟后开始计时,按<Ctrl+C>开关...\n",
        it.it_value.tv_sec);

    if (setitimer (ITIMER_REAL, &it, NULL) == -1) 
    {
        perror ("setitimer");
        return -1;
    }

    for (;;) pause ();

    return 0;
}
Unix/Linux 系统编程中用于管理信号处理行为的核心概念或模型 Unix/Linux信号处理框架包含信号模型、处理行为控制、关键机制和编程模型。信号是异步事件通知,分为生成、递送和处理三个阶段。处理行为包括默认、忽略和自定义三种类型。关键机制涉及信号阻塞/屏蔽,防止重入和信号丢失。系统提供sigaction()等调用来管理信号,并支持单次/持久/实时三种处理模型。特殊场景需考虑系统调用中断、信号竞争和子进程终止。安全编程要求使用异步信号安全函数、自包含状态和备选信号栈。这些概念共同构成了完整的信号处理体系,需要开发者深入理解其交互机制才能编写健壮代码。 阅读详情

相关推荐

深入UNIX环境的高级编程实践指南

Unix/Linux系统编程是构建在操作系统提供的底层接口之上的高级编程技术,它允许程序员编写更为接近硬件的代码,以实现高效的资源管理和任务执行。本章将为读者概述系统编程的基础知识,包括进程管理、文件I/O、信号处理以及网络通信等关键概念。通过本章节的介绍,我们深入了解了文件I/O的基本操作、高级技术、文件系统的层次结构和性能调优方法。掌握这些知识对于进行高效和安全的文件管理至关重要。接下来,我们将继续探讨进程管理与控制的相关技术,进一步提升我们的系统编程能力。

weixin_34064233的博客 790

【C++上层应用】信号/中断

不管我们想在程序中捕获什么信号,必须使用 signal 函数来注册信号,并将其与信号处理程序相关联。

普罗米修斯的博客 443

Linux/Unix系统编程权威指南:深入实践手册

在Linux/Unix系统中,进程是一个程序的执行实例,它包含程序代码、其当前的活动、分配给它的处理器时间、分配给它的内存空间以及系统资源等。进程是系统进行资源分配和调度的一个独立单位。进程主要分为以下几类:用户进程:这是用户应用程序运行时创建的进程。用户进程通常在用户空间运行,并通过系统调用请求操作系统服务。系统进程:这是由操作系统启动并执行特定系统任务的进程,例如守护进程。这些进程通常拥有比用户进程更高的权限。内核线程:这些是内核自身执行的操作,表现为内核线程。

weixin_42376614的博客 1031

进程信号>>>种类、注册、捕捉

信号的基本概念 信号是一个软件中断,打断当前正在运行的进程,让该进程去处理信号的事件。当前的进程需要处理信号所带来的操作。 信号的种类 Linux操作系统当中,总共有62种信号 1~31 不可靠信号,信号有可能会丢失,非实时信号 34~64 可靠信号,信号是不可能被丢失的,实时信号 信号的产生 硬件的产生: 硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进...

这是书生呀 451

全面掌握Unix/Linux系统编程实践教程

进程是操作系统进行资源分配和调度的一个独立单位。每个进程都拥有自己的地址空间,代码、数据和其他资源。现代操作系统通过进程控制块(PCB)来管理进程,PCB包含了进程的PID、状态、程序计数器、寄存器集合和内存管理信息等。进程具有并发性、独立性、异步性和结构性四大特性。并发性指进程在操作系统中可以同时存在和运行,独立性表明进程在执行时互不影响,异步性是指进程按照各自独立的、不可预知的速度向前推进,结构性体现在进程由代码段、数据段和PCB等组成。

weixin_35734408的博客 754

使用KITTI数据集测试不同算法的性能

该文章将使用同一个kitti数据集来测试不同算法的性能,目前已经测试的算法包括:LIO-SAM,FAST-LIO2,Point-LIO,Faster-LIO。

wwtx的博客 2586

【Linux内核】中断信号

这里写目录标题中断同步中断(异常)异步中断(中断)中断处理程序 中断 一种电信号,由硬件设备产生,并直接送入中断控制器的输入引脚上,再由中断控制器向处理器发送相应的信号。处理器检测到该信号,便中断自己当前正在处理的工作,转而去处理中断。此后,处理器会通知OS已经产生中断。这样,OS可以对这个中断进行适当的处理。 不同的设备对应的中断不同,每个中断都通过一个唯一的数字标识,这些值通常被称为中断请求线(IRQ)。 同步中断(异常) 由CPU本身在执行程序的过程中产生的中断指令,是主动的。 该中断指令执行完毕后,

尚拙的博客 1084

AI大模型应用性能核心指标:TTFT 与 TPOT 详解

这两个指标专门衡量大模型流式响应性能,是评估用户体验的关键指标。· 是什么:从请求到第一个输出的等待时间· 为什么重要:决定用户的第一印象和响应感知· 优化方向:模型加载、预处理、冷启动优化· 行业标准:最好<500ms,可接受<1.5s· 是什么:每个输出token的平均生成时间· 为什么重要:决定流式输出的流畅度和总完成时间· 优化方向:推理引擎、内存带宽、批处理优化· 行业标准:最好<50ms/token,可接受<100ms/token在Dify/大模型应用中的意义。

lovehu6686的博客 3942

【C/C++】C语言signal信号|软中断信号|程序捕获Ctrl+C 信号

http://www.21shipin.com/html/61445.shtml 1、概念 信号是Linux编程中非常重要的部分。 信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断。它的实质和使用很象中断。所以,信号可以说是进程控制的一部分。 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互...

我的笔记本 2832

代谢组+微生物组联合分析实战:从样本处理到生物标志物筛选的完整流程

本文详细介绍了代谢组与微生物组联合分析的全流程,从样本采集、处理到生物标志物筛选的科学实践。重点解析了样本类型匹配、共提取技术、质控协同监控及多维度关联分析方法,帮助研究者系统掌握这一前沿技术,为疾病机制研究和精准医疗提供新视角。

weixin_28335283的博客 215

解决ublox无法定位问题(一直输出$GxTXT)

最近开始研究有关RTK技术的项目,于是先购买了两块ublox公司的NEO-M8T-0型号芯片。(注意NEO-M8T还细分为NEO-M8T-0和NEO-M8T-2,前者不支持RTCM输出,后者支持RTCM输出) 首先都是按常规步骤,直接把芯片模块串口接电脑调试,这边推荐是用官网的u-center调试软件。GPS芯片大多数都是上电后串口自动发送定位等信息,这两块芯片有一块正常情...

众缈之门的博客 7358

【Linux系列】Unix 和类 Unix 系统中的信号处理:SIGINT 与 SIGHUP

信号处理是 Unix 和类 Unix 系统中进程间通信的重要机制之一。通过合理地捕获和处理 SIGINT 和 SIGHUP 等信号,可以提高程序的健壮性和用户体验。开发者应深入理解信号的工作原理,并在实际编程中合理应用信号处理技术,以编写出更加稳定和可靠的应用程序。👍🏻呗。❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄👍 👍 👍🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙。

优快云站内信: https://i.youkuaiyun.com/#/msg/chat/qyj19920704 1万+

JasperReport制作行内容合并的表格

效果图: 实现思路一: 交叉表 另一个思路: 普通表格 缺点:无法实现内容 垂直居中显示 准备工作 一、数据准备 DROP TABLE IF EXISTS `address_item_sex_value`; CREATE TABLE `address_item_sex_value` ( `id` int(11) NOT NULL AUTO_INCREM...

weixin_34148456的博客 2466

《UNIX 环境高级编程》核心思想讲座课件

被誉为UNIX编程"圣经"的经典著作作者:W. Richard Stevens和Stephen A. Rago第1版(1992年)、第2版(2005年)、第3版(2013年)的演进Dennis Ritchie(UNIX和C语言之父)评价:“这是一本弥足珍贵的经典著作”进程间通信的需求与挑战UNIX/Linux中的IPC机制分类IPC机制的选择依据深入理解操作系统工作原理掌握底层编程技能的重要性UNIX设计哲学在现代软件开发中的应用。

AI天才研究院 1187

YOLOv8推理程序

YOLOv8单独推理,有时候我们自定义的模块算子无法正常转换为其他框架,而且需要做成应用,因此需要一个单独推理的程序,返回的是识别后的照片还有各个类别及其对应数量。文章最后给出Flask封装为Server以及调用的实例还有Client请求代码,支持几十路多线程并发,只需加载一次模型,后边推理即可。

AIOT魔法师 698

卸载python弹框报错:Could not set file security for file ‘D:\Config.Msi\abd5.rbf‘. Error: 5. Verify...如何解决?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值 。

**My Coding Family** 6476

《UNIX环境高级编程》读书笔记01: 开始读一本被誉为“圣经”的Unix编程巨著

根据人民邮电出版社第3版官方介绍,全书共计21章,页数多达953页,系统阐述了Unix环境下的文件I/O、进程控制、信号处理、线程同步及网络进程间通信等核心机制。书中提供了超过1万行的ISO C代码示例和400余个系统调用案例,内容覆盖Solaris、macOS等主流操作系统平台。《Unix环境高级编程》不只是一本书,它是一张通往系统编程世界的入场券。读完它,你将获得:对Unix/Linux系统编程的系统性理解、编写健壮系统级程序的实际能力、阅读开源项目底层代码的能力、在技术面试中回答系统编程问题的自信。

1789

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

UNIX-Linux信号处理

UNIX-Linux信号处理信号的基本概念中断信号常见的信号不可靠信号可靠信号信号的来源信号的处理信号的捕获发送信号键盘错误命令函数暂停和休眠时钟信号集与信号屏蔽信号集信号屏蔽带附加信息的信号计时器系统为每个进程维护三个计时器 信号的基本概念 中断 中止(不是终止)当前正在执行的任务,转而执行其他任务(可能返回也可能不返回),中断分为硬件中断(硬件设备产生的中断)和软件中断(其他程序产生的中断)。...

m0_37622246的博客 299

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Unix信号处理

当更改信号动作时,如果sa_handler指向一个信号捕捉函数(不是常数SIG_IGN或者SIG_DFL),则sa_mask字段说明了一个信号集,在调用信号捕捉函数之前,该信号集要加到进程的信号屏蔽集。比较特殊的是,exec函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不变(一个进程原先要捕捉的信号,当其执行一个新程序后,就自然不能再被捕捉了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义)。当捕捉到一个信号时,进入信号捕捉函数,此时当前的信号被自动加到进程的信号屏蔽字中。

weixin_43979090的博客 1041

linux程序设计 unix环境高级编程,unix环境高级编程(第3版)

20多年来,严谨的C程序员都是依靠一本书来深入了解驱动UNIX和Linux内核的编程接口的实用知识的,这本书就是W. Richard Stevens所著的《UNIX环境高级编程》。现在,Stevens的同事Stephen Rago彻底更新了这本经典著作。 新的第3版支持当今领先的系统平台,反映了*技术进展和*实践,并且符合*的Single UNIX Specification第4版(SUSv4)。...

weixin_29018335的博客 467

Unix环境高级编程-学习-11-信号之初识

本文介绍Unix环境高级编程-信号,包含信号宏、信号函数、信号DEMO等,又多学会一点,勤能补拙,继续加油吧。

qq_45111959的博客 877

【亲测免费】 《UNIX环境高级编程》中文第三版笔记教程

`APUE_notes` 是一个关于《UNIX环境高级编程》(Advanced Programming in the UNIX Environment)第三版的中文笔记项目。该项目由 huaxz1986 维护,旨在帮助读者更好地理解和掌握 UNIX 环境下的高级编程技术。笔记内容涵盖了书籍的主要章节,提炼了重点和关键点,并提供了示例代码和运行结果截图,帮助读者更好地理解和应用书中的知识。 ##

gitblog_00490的博客 587

《Linux-UNIX系统编程手册》读书笔记

1)fork()时,子进程的代码段共享父进程的代码段物理页,对于父进程数据段、堆段和栈段中的各页,内核采用写时复制(copy-on-write)技术来处理,来避免性能浪费;2)exit()时,内核会回收进程使用的资源,但是在wait()之后,内核才会回收该进程的task结构资源;3)父进程可以通过捕获SIGCHLD 信号来得知子进程的退出动作;4)exec()除了装载程序外,还可以装载解释器脚本;

paxwuliqi的博客 1218

UNIX环境高级编程笔记

UNIX环境高级编程 这本书应该算是圣经了,全部认真学完不太现实,所以打算粗略学一遍,只在自己认为重要的地方详略进行笔记

qq_55537010的博客 3450
上一篇: UNIX-Linux环境编程(四):文件系统(下)
下一篇: UNIX-Linux环境编程(五):进程管理
perror_0
博客等级 码龄7年 14粉丝 15原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值