共享内存通信
共享内存,顾名思义就是允许俩个不相干的进程访问同一个逻辑内存,共享内存是俩个正在运行读的进程之间共享和传递数据的一种非常有效的方式,不同进程之间共享的内存的内存通常为同一段物理内存。进程可以将同一段物理内存连接到它们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
共享内存的原理
在LINUX中,每个进程都有属于自己的进程控制块(PCB)和地址空间(AddrSpace),并且都有一个与之对于的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理,两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
共享内存的特点
(1)共享内存是一种最高效的进程间的通信方式,进程可以直接读写内存,而进程之间不需要通过任何数据的拷贝。
(2)内核中有一块供多个进程交换信息的内存区,可由需要访问的进程将内存区映射到自己的私有地址空间。
(3)进程之间可以读写内核的这一内存区,而不需要进行数据拷贝。
(4)由于出现多个进程共享一段内存的情况,需要依靠互斥锁,信号量等同步机制来实现内存共享的功能。
共享内存的创建过程
1.首先要申请共享内存,也就是在物理地址中开辟好共享内存空间,供进程间通信使用。而且我们知道物理地址中的共享内存可能会有很多个,那么肯定就要管理这些内存,所以要管理我们就要先描述再组织,所以出了创建共享内存之外,还要为共享内存创建描述共享内存的内核数据结构。
2.将共享内存挂接到地址空间,本质上就是给两个进程之间的共享内存和虚拟地址空间之间建立映射,之后就可以开始进程间的数据传输与修改了。
3.用完之后要去掉共享内存和进程地址空间之间的联系,本质上就是修改页表,取消共享内存和虚拟内存的映射关系。
4.释放共享内存,将内存归还给系统。
共享内存接口函数及指令
1.查看系统中的共享存储段
ipcs -m
2.删除系统中的共享存储段
ipcrm -m 【shmid】
3.shmget()创建共享内存
int shmget(key_t key , size_t size, int shmflg);
参数1:key:由ftok生成的key标识
参数2:size:需要申请共享内存的大小
参数3:shmflg:访问权限(和文件权限一致)
如果是共享内存不存在,则需要使用IPC_CREAT或IPC_EXCL创建共享内存
如果是共享内存已经存在,可以使用IPC_CREAT或直接转0,获得共享内存
返回值:
成功返回一个新建或已经存在的共享内存标识符,取决于shmflg的参数
失败返回-1
4.shmat()挂接共享内存
void *shmat(int shmid,const void *shmaddr,int shmflg);
参数1:shmid:共享存储段的标识符
参数2:shmaddr:一般设为NULL,则存储段连接到由内核选择的第一个可以用的地质上
参数3:shmflg:若指定SHM_RDONLY,则以只读的方式连接此段,0是可以读写自由
返回值:
成功返回共享存储段的指针(虚拟地址)
失败返回-1
5.shmdt()去关联共享内存
当一个进程不需要共享内存的时候,就需要去关联。该函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程。
int shmadt(const void *shmaddr);
参数:shmaddr:连接以后返回的地址
返回值:
成功返回0
失败返回-1
6.shmctl()销毁共享内存
int shmctl(int shmid ,int cmd ,struct shmid_ds *buf);
参数1:shmid:共享存储段标识符
参数2:cmd:指定的执行操作,设置为IPC_RMID 时表示可以删除共享内存
参数3:buf:设置为NULL即可
返回值:
成功返回0
失败返回-1
例程:(1) fork两个子进程,server和client, 进行通信;
(2) server 端建立一个key为75的共享区,并将第一个字节置为-1,作为数据空的标志。等待其他进程发 来的消息。当该字节的值发生变化时,表示收到了信息,进行处理。然后再次把它的值设为-1,如果遇到的值为0,则视为为结束信号,取消该队列,并退出server。 server 每接收到一次数据后显 示"(server)received”。
(3) client 端建立一个key为75的共享区,当共享取得第一个字节为-1时,server端空闲,2可发送请求。 client 随即填入9到0。期间等待sserver端的再次空闲。进行完这些操作后,client退出。client每发送一次 数据后显示"(client)sent"。
(4)父进程在server和client 均退出后结束 。
include
#include
#include
#include
#include
#include
#include
#define SHMKEY 75//定义键值,也可通过ftok进行生产
int shmid,i;
int *addr = NULL;
void client()
{
int i;
shmid = shmget(SHMKEY,1024,0777);//获得已有的共享内存段
if(shmid == -1)
{
perror("shmget");
exit(1);
}
printf("client() shmid = %d\n",shmid);
addr = (int *)shmat(shmid,NULL,0);//挂接共享内存
for(i=9;i>=0;i--)
{
while(-1 != *addr);
printf("(client) server save *addr = %d\n",*addr);
*addr = i;
printf("(client) sent *addr = %d\n",i);
}
exit(0);
}
void server()
{
shmid = shmget(SHMKEY,1024,0777|IPC_CREAT);
if(shmid == -1)
{
perror("shmid");
exit(1);
}
printf("server() shmid = %d\n",shmid);
addr = (int *)shmat(shmid,NULL,0);
do
{
*addr = -1;
while(*addr == -1);
printf("(server) received addr = %d\n",*addr);
}while(*addr);
shmctl(shmid,IPC_RMID,0);
exit(0);
}
int main()
{
while(-1==(i = fork()));
if(!i)
{
server();
}
system("ipcs -m");
while(-1 == (i=fork()));
if(!i)
{
client();
}
wait(NULL);
wait(NULL);
system("ipcs -m");
return 0;
}
进程组和会话
进程组,每个进程组有一个领头进程。进程组是一个或多个进程的集合,通常它们与一组作业相关联, 可以接受来自同一终端的各种信号。 每个进程组都有唯一的进程组ID(整数,也可以存放在pid_t类型中)。进程组由进程组ID来唯一标识。除 了进程号(PID)之外,进程组ID也是一个进程的必备属性之一。
getpgrp:获得进程组id,即领头进程的pid
头文件:#include
pid_t getpgrrp(void)
//返回值:调用进程的进程组id
创建会话
创建一个会话需要注意以下6点:
1.调用进程不能时进程组的组长,该进程变成新会话首进程。
2.该进程成为一个新进程组的组长进程。
3.需要root权限(ubuntu不需要)。
4.新会话丢弃原有的控制终端,该会话没有控制终端。
5.该调用进程是组长进程,则出错返回。
6.建立新会话时,先调用fork(),父进程终止,子进程调用setsidu
getsid函数
获取进程所属的会话id
pid_t getsid(pid_t pid);
返回值:
成功:返回调用进程的会话1D;
失败: -1;
设置errno 参数: pid为0表示查看当前进程session ID;
ps ajx 命令查看系统中的进程。
参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,
参数x表示不仅列有控制終端的进程,也列出所有无控制終端的进程,
参数j表示列出与作业控制相关的信息。
小组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
setid函数
创建一个会话,并以自已的ID设置进程组ID,同时也是新会话的ID。
pid_t setsid(void);
返回值:
成功: 返回调用进程的会话ID;
失败: -1. 设置errno
调用了setsid函数的进程,既是新的会长,也是新的组长。
例程: fork一个子进程,并使其创建一个新会话。 查看进程组ID、会话ID前后变化
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main()
{
pid_t pid;
if((pid=fork())<0)
{
sys_err("fork err");
}
else if(pid==0)
{
printf("child process PID is %d\n" ,getpid());
printf("Group ID of child is %d\n", getpgid(0));
printf("Session ID of child is %d\n",getsid(0));
sleep(10);
//子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话>
setsid();
printf( "Changed:\n" );
printf("child process PID is %d\n", getpid());
printf("Group ID of child is %d\n",getpgid(0));
printf("Session ID of child is %d\n", getsid(0));
sleep(20);
exit(0);
}
return 0;
}
守护进程
守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统 引导的时候启动,并且一直运行到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。
守护进程是一个在后台运行并且不受任何终端控制的进程。Unix操作系统有很多典型的守护进程(其数 目根据需要或20~50不等),它们在后台运行,执行不同的管理任务。
创建守护进程的步骤:
1.创建子进程, 父进程退出 所有工作在子进程中进行形式上脱离了控制終端
2.在子进程中创建新会话 setsid)函数 使子进程完全独立出来,脱离控制
3.改变当前目录(比如为根目录) chdir()函数 防止占用可卸载的文件系统 也可以换成其它路径
4.重设文件权限掩码 umask()函数 防止继承的文件创建屏蔽宇拒绝某些权限 增加守护进程灵活性
5.关闭文件描述符。 继承的打开文件不会用到,浪费系统资源,无法卸载
6.开始执行守护进程核心工作守护进程退出处理程序模型
#include
#include
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main()
{
pid_t pid;
int ret,fd;
pid = fork();
if (pid == -1)
{
sys_err("setsid error");
}
else if (pid > 0)
{
exit(0);//父进程终止
}
else
{
pid = setsid();//创建新会话
if (pid == -1)
{
sys_err("setsid error" );
}
ret=chdir("/home/xw/");//改变工作目录位置,保证目录有效
if(ret==-1)
{
sys_err("chdir err");
}
umask(0022);//改变文件访问权限掩码
close(STDIN_FILENO);//关闭文件描述符 0
fd=open("/dev/null",O_RDWR); //fd ---> 0
if(fd==-1)
{
sys_err("open err");
}
dup2(fd,STDOUT_FILENO);//重定向
dup2(fd,STDERR_FILENO);//重定向
while(1); //模拟守护进程业务逻辑需求
}
return 0;
}
信号
信号在我们的生活中随处可见,如: 古代战争中摔杯为号;现代战争中的信号弹;体育比赛中使用的信号..... 他们都有共性: 1.简单2.不能携带大量信息3.满足某个特设条件才发送。 信号是信息的载体,Linux/UNIX 环境下,古老、经典的通信方式,现下依然是主要的通信手段。 Unix早期版本就提供了信号机制,但不可靠,信号可能丢失。Berkeley 和AT&T都对信号模型做了 更改,增加了可靠信号机制。但彼此不兼容。POSIX.1 对可靠信号例程进行了标准化。
信号机制
信号机制是一种使用信号来进行进程之间传递消息的方法,信号的全称为==软中断信号==,简称软中 断。信号的本质是软件层次上对中断的一种模拟(软中断)。它是一种异步通信的处理机制,事实上, 进程并不知道信号何时到来。 比如:A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都 要暂停运行,去处理信号,处理完毕再继续执行。 信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这 个延迟时间非常短,不易察觉。
每个进程收到的所有信号,都是由内核负责产生和发送的,由内核处理。 信号是软件层面上的“中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号, 处理结束,再继续执行后续指令。
与信号相关的事件和状态
产生信号:
1.按键产生,当用户按下某些中断按键后引发某些终端产生信号,如: Ctrl+c(终止进程)、 Ctrlt+z (暂停进程到后台)
2.系统调用产生,如: kill、 raise、 abort,可以给一个进程或进程组发送一个信号,此时发送和接 收信号的进程或者进程组的所有者必须相同。
3.软件条件产生,如:定时器alarm(比如sleep函数)
4.硬件异常产生,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误).这种异常 信号通常会由硬件检测并通知linux内核,然后内核产生信号发送给相关进程
5.命令产生,如:用户调用kill命令将信号发送给其他进程
递达
递送并且到达进程,由内核处理掉
未决
产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。
信号的处理方式:
Linux的每一个信号都有一个缺省的动作,典型的缺省动作是终止进程,当一个信号到来 的时候收到这个信号的进程会根据信号的具体情况提供以下三种不同的处理方式:
(1)捕捉:对于需要处理的信号,由用户的函数来处理。
(2)忽略:丢弃某个信号,对该信号不做任何处理。
(3)按系统的默认值处理,这种缺省操作大多数使得进程终止,进程通过系统调用signal函数来指定进 程对某个信号的处理行为。
Linux内核的进程控制块PCB是一个结构体,task struct, 除了包含进程pid,工作目录,用户id, 组id,文 件描述符表,还包含了信号相关的信息,主要指==阻塞信号集==和==未决信号集==。
阻塞信号集
将某些信号加入集合,对他们设置屏蔽(对应位设置为1),当屏蔽x信号后,再收到该信号,该信号属 于未决信号集,直到解除屏蔽后才能处理。
未决信号集:
1.动作:信号产生后,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理 对应位翻转回为0。这一时刻往往非常短暂。
2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前, 信号一直处于未决状态。
信号编号
在头文件中定义了64种信号,这些信号的名字都以SIG开头,且都被定义为正整数,称为信号 编号。 查看所有信号===>使用命令:kill -l
不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64 称之为实时 信号,驱动编程与硬件相关。名字上区别不大。而前32个名字各不同。 普通信号:场景应用程序开发使用,信号都有默认处理动作, 实时信号:场景底层驱动开发使用,信号处理可以由用户处理 。
信号四要素
与变量三要素类似的,每个信号也有其必备四要素,
分别是: 1.编号 2.名称 3.事件 4.默认处理动作
使用信号前:应先确认其四要素,再使用 可通过man 7 signal查看帮助文档获取。也可查看/usr/include/signal.h 在标准信号中,有一些信号是有三个“Value",第一个值通常对alpha 和sparc架构有效,中间值针对 x86、arm 和其他架构,最后一个应用于mips架构。 一个 ’_‘ 表示在对应架构上尚未定义该信号。 不同的操作系统定义了不同的系统信号。因此有些信号出现在Unix系统内,也出现在Linux中,而有的 信号出现在FreeBSD或Mac os中却没有出现在Llinux下。这里我们只研究Linux系统中的信号。 默认动作: Term:终止进程。 lgn:忽略信号 (默认即时对该种信号忽略操作)。 Core:终止进程,生成Core文件。(查验进程死亡原因,用于 gdb调试)。 Stop:停止(暂停)进程。 Cont:继续运行进程。 注意从man 7 signal帮助文档中可看到: The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored. 这里特别强调了9) SIGKILL和19) SIGSTOP信号,不允许忽略和捕捉,只能执行默认动作。甚至不能将 其设置为阻塞。 另外需清楚,只有每个信号所对应的事件发生了,该信号才会被递送(但不一定递达), 不应乱发信号!
Linux常规信号一览表
1. SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程。
2. SIGINT:当用户按下了组合键时,用户终端向正在运行中的程序发送此信号。默认动作为 终止进程。
3. SIGQUIT:当用户按下组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序 发出些信号。默认动作为终止进程。
4. SIGILL: CPU 检测到某进程执行了非法指令。默认动作为终止进程并产生core 文件。
5. SIGTRAP:该信号由断点指令或其他trap 指令产生。默认动作为终止里程并产生core文件。
6. SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
7. SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
8. SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算 法错误。默认动作为终止进程并产生core文件。
9. SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理 员提供了可以杀死任何进程的方法。
10. SIGUSR1:用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11. SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
12. SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进 程。
13. SIGPIPE: Broken pipe向. -一个没有读端的管道写数据。默认动作为终止进程。
14. SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
15. SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常 退出。执行shell命令Kill 时,缺省产生这个信号。默认动作为终止进程。
16. SIGSTKFLT: Linux 早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。
17. SIGCHLD:子进程状态发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。
18. SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。
19. SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
20. SIGTSTP:停止终端交互进程的运行。按下组合键时发出这个信号。默认动作为暂停进 程。。
21. SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。
22. SIGTTOU:该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
23. SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网 络带处 数据到达,默认动作为忽略该信号。
24. SIGXCPU:进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程。默认 动作为数据到达,默认动作为忽略该信号。
25. SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。
26. SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的 使用时间。默认动作为终止进程。.
27. SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动 作为终止进程。
28. SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。
29. SIGIO:此信号向进程指示发出了一个异步10事件。默认动作为忽略。
30. SIGPWR:关机。默认动作为终止进程。
31. SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。
34.SIGRTMIN ~ (64) SIGRTMAX: UINUX 的实时信号,它们没有固定的含义(可以由用户自定义)。所有 的实时信号的默认动作都为终止进程。
信号的产生
1.终端按键产生信号 Ctrl+c→2) SIGINT (终止/中断) "INT" --interrupt Ctrl+z→20) SIGTSTP (暂停/停止) "T" --terminal终端 Ctrl+\→3) SIGQUIT (退出)
2.硬件异常产生信号 除0操作 → 8) SIGFPE (浮点数例外) "P"---- float浮点数。。 非法访问内存 →11) SIGSEGV (段错误)。 总线错误 →7) SIGBUS
kill函数、命令产生信号
kill命令产生信号: kill 所要杀的进程pid 信号名称
kill函数:给指定进程发送指定信号(不一定杀死)。
int kill(pid_t pid, int sig)
返回值:
成功: 0;
失败: -1 (ID非法,信号非法,普通用户杀init进程等权级问题),设置errno.
sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致 pid>0:发送信号给指定的进程
pid=0:发送信号给与调用kill函数进程属于同一进程组的所有进程
pid<0:取|pid|发给对应进程组
pid=-1:发送给有权限发送的系统中所有进程
权限保护:
super 用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。kill -9 (root用户的 pid)是不可以的。 同样,普通用户也不能向其他普通用户发送信号,终止其进程。只能向自己创建的进 程发送信号。
普通用户基本规则是:发送者实际或有效用户ID==接收者实际或有效用户ID。
例程:父进程发送信息给子进程结束标志
#include
#include
#include
#include
#include
#include
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
pid_t pid = fork();
if(pid>0)
{
printf("parent,pid = %d\n", getpid());
while(1);
}
else if(pid==0)
{
printf("child pid = %d, ppid = %d\n", getpid(), getppid());
sleep(2);
kill(getppid(), SIGKILL);
}
return 0;
}
软件产生信号
alarm函数
设置定时器(闹钟)。在指定seconds 后,内核会给当前进程发送14) SIGALRM 信号。进程收到该信号, 默认动作终止。
每个进程都有且只有唯一个定时器。
unsigned int alarm(unsigned int seconds);
参数:定时秒数
返回值:上次给定的剩余时间,无错误现象
常用:取消定时器alarm(0),返回旧闹钟余下秒数。
例: alarm(5) →3sec →alarm(4) →5sec →alarm(5) →alarm(0) 。
定时,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、 终止、僵尸...无论进程处于何种状 态alarm都计时。也称为自然时间计时法。
取消闹钟:alarm(0)
练习:编写程序,测试你使用的计算机1秒钟能数多少个数。
#include
#include
int main(void)
{
int i;
unsigned int ret =alarm(1);
// printf("ret01 =%d\n",ret);
// sleep(1);
// ret =alarm(2);
// printf("ret02 =%d\n",ret);
for(i = 0; ; i++)
{
printf("%d\n", i);
}
return 0;
}
使用time命令查看程序执行的时间。
程序运行的瓶颈在于IO,优化程序,首选优化IO。
实际执行时间 = 系统时间 + 用户时间 + 等待时间。
setitimer函数
设置定时器(闹钟)。可代替 alarm函数。精度微秒us,可以实现周期定时
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
//返回值:
成功: 0;
失败: -1, 设置errno
//参数1: which: 指定定时方式 自然定时:ITIMER_REAL 计算自然时间。
虚拟空间计时(用户空间): ITIMER_VIRTUAL 只计算进程占用cpu的时间。
运行时计时(用户+内核): ITIMER_PROF 计算占用cpu及执行系统调用的时间。
//参数2:new_value:定时秒数
struct itimerval
{
struct timeval
{
time_t tv_sec; /* seconds */
suseconds_t tv_usec: /* microseconds */
}it_interval;
struct timeval
{
time_t tv_sec; /* seconds */
suseconds_t tv_usec: /* microseconds */
}it_value;
}
//参数3:old_value:传出参数,上次定时剩余时间
old_value參数,它是用来存储上一次setitimer调用时设置的new_value值。 通经常使用不上,设置为NULL。
提示:
it interval: 用来设定两次定时任务之间间隔的时间。
it value:用于设定延时时间。
settimer工作机制是,先对it_value倒计时,当it_value倒计时为零时触发信号。然后触发it_interval 倒计时。倒计时为零时,触发信号,处理信号;同时it_interval再次进行倒计时,一直这样循环下去。
两个参数都设置为0,即清0操作。
//参数2:初始化eg:
struct itimerval new_t;
new_t.it_interval.tv_sec=1;
new_t.it_interval.tv_usec = 0;
new_t.it_value.tv_sec = 0;
new_t.it_value.tv_usec = 0;
练习:使用setitimer函数实现alarm函数,重复计算机1秒数数程序。
#include
#include
#include
#include
void myfunc(int signo)
{
printf("hello world\n");
exit(1);
}
int main(void)
{
// struct itimerval 结构体原型,代码后面有解释
struct itimerval newit;
signal(SIGALRM,myfunc); //注册SIGALRM信号的捕捉处理函数。
newit.it_value.tv_sec = 1;
newit.it_value.tv_usec = 0;
newit.it_interval.tv_sec = 2;
newit.it_interval.tv_usec = 0;
if(setitimer(ITIMER_REAL, &newit, NULL) == -1)
{
perror("setitimer error");
return -1;
}
int i=0;
while(1)
{
i++;
printf("i=%d\n",i);
}
return 0;
}
延时2秒启动输出helloworld,间隔5s重复输出。
#include
#include
#include
void myfunc(int signo)
{
printf("hello world\n");
}
int main(void)
{
struct itimerval newit, oldit;
signal(SIGALRM,myfunc); //注册SIGAL RM信号的捕捉处理函数。
newit.it_value.tv_sec = 2;
newit.it_value.tv_usec = 0;
newit.it_interval.tv_sec = 5;
newit.it_interval.tv_usec = 0;
if(setitimer(ITIMER_REAL, &newit, &oldit) == -1)
{
perror("setitimer error");
return -1;
}
while(1);
return 0;
}