(1)终端按键产生信号(与终端交互的进程)
Ctrl + c → 2) SIGINT(终止/中断) "INT" ----Interrupt
Ctrl + z → 20) SIGTSTP(暂停/停止) "T" ----Terminal 终端 此时进程处于后台运行
Ctrl + \ → 3) SIGQUIT(退出)
(2)硬件异常产生信号
除0操作 → 8) SIGFPE (浮点数例外) "F" -----float 浮点数。
非法访问内存 → 11) SIGSEGV (段错误)
总线错误 → 7) SIGBUS(内存未对齐)
(3)kill函数/命令产生信号
注意:kill函数或命令产生信号并不一定是要杀死进程,信号的含义是根据编号(或宏)来决定的,不是kill本身的含义。
kill命令向指定进程发送指定信号: kill –SIGKILL pid // 终止该进程,向该进程发送9号信号
kill函数向指定进程发送指定信号:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
作用:向指定进程(pid)发送指定信号(sig),指定信号最好用宏表示,不用编号,因为在不同类型的操作系统中,信号的编号可能会不一样,用宏表示可以增强移植性。
返回值:成功0,失败-1(ID非法;信号非法;普通用户杀init进程等权级问题,即权限不够)。
pid > 0:发送信号给指定的进程。
pid = 0:发送信号给与调用kill函数进程属于同一进程组的所有进程(包括本身)。
pid < 0:取|pid|发给对应进程组。
pid = -1:发送给进程有权限发送的系统中所有进程(包括本身)。
进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。
权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。 只能向自己创建的进程发送信号。普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID
(4)raise和abort函数(系统调用产生,包括上面的kill函数)
raise函数:给当前进程发送指定信号(自己给自己发),则有:raise(signo) == kill(getpid(), signo);
#include <signal.h>
int raise(int sig); 成功:0,失败非0值
abort 函数:给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件
#include <stdlib.h>
void abort(void);
(5)alarm、setitimer函数(软件条件产生)
设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止(结束进程)。每个进程都有且只有唯一个定时器。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);成功:返回旧闹钟剩余的秒数,即该进程上一次调用alarm函数后还剩下的秒数;或者返回0(第一次调用alarm函数的时候)。无失败。
注意:alarm(0)表示取消闹钟,不是说0秒后终止进程,是取消闹钟。另外:定时与进程状态无关(自然时间),就绪、运行、挂起(阻塞、暂停)、终止、僵尸等,无论进程处于何种状态,alarm都计时。
例:alarm(5) → 3sec → alarm(4) → 5sec → alarm(5) →0sec→ alarm(0)
alarm(5)返回0 alarm(4)返回2 alarm(5)返回0 紧随着马上的alarm(0)返回5,并且取消闹钟。
time命令。 time ./zsx 查看程序zsx执行的时间,时间由三部分组成:程序在进程系统空间运行的时间+用户空间的时间+等待的时间(等待CPU、各种硬件资源等)=程序实际执行的时间。程序运行的瓶颈在于IO,优化程序,首选优化IO。
//练习:编写程序,测试你使用的计算机1秒钟能数多少个数。
#include <stdio.h>
#include <unistd.h>int main(void)
{int i;alarm(1);for(i = 0; ; i++)printf("%d\n", i);return 0;
}
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
作用:类似alarm函数,有四点不一样:1.setitimer函数对时间的计算方式有三种,用which参数指定,而alarm函数只有一种(自然时间);2. setitimer函数可以进行周期定时,即等待it_value时间后执行某一动作(收到信号),以后每隔it_interval时间再执行这一动作。而alarm函数只能实现等待it_value时间后执行某一动作(收到信号);3. setitimer函数可以精确到us级别,即其时间为tv_sec与tv_usec之和;4.返回值不一样。
返回值:成功0;失败-1,设置errno
参数which指定定时方式:1.自然定时:ITIMER_REAL → 14)SIGALRM 计算自然时间;2.虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM 只计算进程占用cpu的时间;3.运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF 计算占用cpu及执行系统调用的时间(即相对于自然时间,不包括等待时间)。
参数new_value为传入参数,指定计时的大小,与alarm的参数作用一样;
参数old_value为传出参数,指定就旧闹钟剩余的秒数,作用类似alarm的返回值;结构体详细描述如下:
struct itimerval {
struct timeval it_interval; /* next value */ 下一次定时的值(间隔时间)
struct timeval it_value; /* current value */ 当前定时的值
}; // it_interval或it_value设置为0表示清零操作,即取消定时。
struct timeval {
time_t tv_sec; /* seconds */ s
suseconds_t tv_usec; /* microseconds */ us
};
注意:每个进程都有且只有唯一个定时器。setitimer函数和alarm函数共用同一个定时器(闹钟)。
//练习: 使用setitimer函数实现alarm函数,重复计算机1秒数数程序。
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>unsigned int my_alarm(unsigned int sec) //实现alarm函数
{struct itimerval it, oldit;int ret;it.it_value.tv_sec = sec;it.it_value.tv_usec = 0;it.it_interval.tv_sec = 0;it.it_interval.tv_usec = 0;ret = setitimer(ITIMER_REAL, &it, &oldit);if (ret == -1) {perror("setitimer");exit(1);}return oldit.it_value.tv_sec;
}int main(void)
{int i;my_alarm(1); //alarm(sec);for(i = 0; ; i++)printf("%d\n", i);return 0;
}
//拓展练习:测试it_interval、it_value这两个参数的作用。
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>void myfunc(int signo)
{printf("hello world\n");
}int main(void)
{struct itimerval it, oldit;signal(SIGALRM, myfunc); //注册SIGALRM信号的捕捉用户处理函数,signal函数用于对信号进行注册,真正捕捉该信号的是内核,内核捕捉该信号后就会去调用signal注册时对应的用户处理函数。 信号捕捉函数signal是一个典型的回调函数it.it_value.tv_sec = 5;it.it_value.tv_usec = 0;it.it_interval.tv_sec = 3;it.it_interval.tv_usec = 0;if(setitimer(ITIMER_REAL, &it, &oldit) == -1){perror("setitimer error");return -1;}while(1);return 0;
}
[root@localhost 01_signal_test]# ./setitimer1
hello world //等5s
hello world //等3s
hello world //等3s
hello world //等3s