距离上一次利用高并发技术实现360度行车记录仪功能已经过去半年了。开始写一系列关于系统编程和网络编程内容进行总结。
温故而知新,欢迎大家讨论学习。
10-06
复习时间
信号是软件中断,很多比较重要的应用程序都需要处理信号。信号是一种进程之间或者内核与进程间异步通信的一种机制,例如:用户在终端键入中断键,会通过信号机制停止一个程序.
A 给 B 发送信号,B 收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,
去处理信号,处理完毕再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称
为“软中断”。
信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延
迟时间非常短,不易察觉。
每个进程收到的所有信号,都是由内核负责发送的,内核处理。
产生信号:
递达:递送并且到达进程。
未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。
信号的处理方式:
Linux 内核的进程控制块 PCB 是一个结构体,task_struct, 除了包含进程 id,状态,工作目录,用户 id,组 id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
阻塞信号集(信号屏蔽字):将某些信号加入集合,对他们设置屏蔽,当屏蔽 x 信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)
未决信号集:
在信号使用之前,要确定其四要素
指令
int kill(pid_t pid, int sig);
成功:0;失败:-1 (ID 非法,信号非法,普通用户杀 init 进程等权级问题),设置 errnosig
:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致pid > 0
: 发送信号给指定的进程。pid = 0
: 发送信号给 与调用 kill 函数进程属于同一进程组的所有进程。pid < 0
: 取|pid|发给对应进程组。pid = -1
:发送给进程有权限发送的系统中所有进程。#include
#include
#include
#include
#define N 5
int main(void)
{
int i;
pid_t pid, q;
for (i = 0; i < N; i++)
{
pid = fork();
if (pid == 0)
{
break;
}
if (i == 2)
q = pid;
}
if (i < 5)
{ //子进程
printf("I'm child %d, getpid = %u\n", i, getpid());
sleep(10);
}
else
{ //父进程
sleep(3);
kill(q, SIGKILL);
printf("------------kill %d child %u finish\n", 2, q);
while (1);
}
return 0;
}
kill -SIGKILL pid
设置定时器(闹钟)。在指定 seconds 后,内核会给当前进程发送 14)SIGALRM 信号。进程收到该信号,默认动作终止。
每个进程都有且只有唯一个定时器
unsigned int alarm(unsigned int seconds);
返回 0 或剩余的秒数,无失败。#include
#include
int main(void)
{
int i;
alarm(1);
for(i = 0; ; i++)
printf("%d\n", i);
return 0;
}
time ./test
time ./test > out
(将结果输入到out) 明显提高性能
结论:程序运行的瓶颈在于 IO,优化程序,首选优化 IO
更具体的细节
man setitimer
查看手册
设置定时器(闹钟)。 可代替 alarm 函数。精度微秒 us,可以实现周期定时
int setitimer
(int which, const struct itimerval *new_value, struct itimerval *old_value); 成功:0;失败:-1,设置errnowhich
:指定定时方式(①自然定时:ITIMER_REAL
)(②虚拟空间计时(用户空间)ITIMER_VIRTUAL
)(③运行时计时(用户+内核):ITIMER_PROF
)参数二
:结构体设置间隔时间和单次时间参数三
:返回旧闹钟的剩余时间(传出参数)it_value
:结构体内部的参数:第一次触发信号的时间it_interval
:结构体内部的参数:用来设定两次定时任务之间间隔的时间(第二次和第一次 or 第三次和第二次之间的时间间隔)#include
#include
#include
#include
#include
#include
#include
unsigned int my_alarm(unsigned int sec)
{
struct itimerval it,oldit;
int ret;
it.it_interval.tv_sec=0;//间隔事件
it.it_interval.tv_usec=0;
it.it_value.tv_sec=sec;//第一次触发时间
it.it_value.tv_usec=0;
ret=setitimer(ITIMER_REAL,&it,&oldit);
if(ret==-1)
perror("setitimer");
return oldit.it_value.tv_sec;
}
int main(void)
{
int i;
my_alarm(1); //默认发射9号信号 默认函数为终止程序
for(i=0;;i++)
printf("%d\n",i);
return 0;
}
控制原理:内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字 mask 可以影响未决信号集。而我们可以在应用程序中自定义 set 来改变 mask。已达到屏蔽指定信号的目的。(重点)
更多函数具体使用
man ***
的方式查看手册
sigset_t set;
// typedef unsigned long sigset_t;int sigemptyset(sigset_t *set);
将某个信号集清 0 成功:0;失败:-1int sigfillset(sigset_t *set);
将某个信号集置 1 成功:0;失败:-1int sigaddset(sigset_t *set, int signum);
将某个信号加入信号集 成功:0;失败:-1int sigdelset(sigset_t *set, int signum);
将某个信号清出信号集 成功:0;失败:-1int sigismember(const sigset_t *set, int signum);
判断某个信号是否在信号集中 返回值:在集合:1;不在:0;出错:-1用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程的信号屏蔽字(PCB 中)
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
成功:0;失败:-1,设置 errnoset
:传入参数,是一个位图,set 中哪位置 1,就表示当前进程屏蔽哪个信号。oldset
:传出参数,保存旧的信号屏蔽集。how
参数取值: 假设当前的信号屏蔽字为 mask
SIG_BLOCK
: 该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。SIG_UNBLOCK
: 该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的补集的交集。set包含了我们希望解除阻塞的信号.SIG_SETMASK
: 自定义set替换mask(mask表示系统自带的阻塞信号集)读取当前进程的未决信号集
int sigpending(sigset_t *set)
; set 传出参数。 返回值:成功:0;失败:-1,设置 errno从结果可以很清楚的看出,当我们按下ctrl+c 未决信号机2号位置变成1,并且一直保持,就是因为我们利用·
sigprocmask
设置了阻塞信号集,信号无法递达。
#include
#include
#include
#include
void printset(sigset_t *ped)//打印操作
{
int i;
for(i = 1; i < 32; i++)
{
if((sigismember(ped, i) == 1))//利用成员判断函数
{
putchar('1');
}
else
{
putchar('0');
}
}
printf("\n");
}
int main(void)
{
sigset_t set, oldset, ped;
sigemptyset(&set);//清空
sigaddset(&set, SIGINT);//设置阻塞 SIGINT
//SIG_BLOCK 表示添加阻塞信号 set表示要覆盖的信号集 oldset表示旧的信号集
sigprocmask(SIG_BLOCK, &set, &oldset);
while(1)
{
sigpending(&ped); //获取未决信号集 传出
printset(&ped); //打印 自定义的函数
sleep(1);
}
return 0;
}
作用:注册一个信号捕捉函数(注册而非创建)
原型:
sighandler_t signal(int signum, sighandler_t handler);
typedef void (*sighandler_t)(int);
//函数指针类型 需要注意的就是需要传入一个整形参数 不管用不用#include
#include
#include
#include
#include
void do_sig(int a)
{
printf("hello world!\n");
}
int main(void)
{
if (signal(SIGINT, do_sig)== SIG_ERR)
{
perror("signal");
exit(1);
}
while (1) {
printf("---------------------\n");
sleep(1);
}
return 0;
}
不做过多记录,用的较少,需要再回来补充
作用:修改信号处理动作(通常在 Linux 用其来注册一个信号的捕捉函数)
原型:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
act
:传入参数,新的处理方式。oldact
:传出参数,旧的处理方式。从结果很容易看出在信号处理函数执行期间,该信号多次递送,那么只在处理函数之行结束后,处理一次。
#include
#include
#include
#include
#include
void do_sig(int a)
{
printf("hello world!\n");
sleep(10);//休息10秒 让函数执行久一点
}
int main(void)
{
if (signal(SIGINT, do_sig)== SIG_ERR)
{
perror("signal");
exit(1);
}
while (1) {
printf("---------------------\n");
sleep(1);
}
return 0;
}
值得说明的是第四步到达第五步,因为函数处理完需要返回(类似于返回值的感觉),在函数处理之前我们实在内核空间,所以还要返回一次。
子进程结束运行,其父进程会收到 SIGCHLD 信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
有几个代码中需要注意的问题 [ 重要]
- 开始设置的信号阻塞,是为了防止子进程结束了,父进程还没有走到注册捕获信号函数。这样就会导致子进程结束信号还是会执行默认的事件–忽略。所以多个了阻塞和解除阻塞
- 自定义事件
do_sig_child
中的while
是为了防止,同一时间多个子进程同时结束,同时发送信号,就会导致 性质二【XXX 信号捕捉函数执行期间,XXX 信号自动被屏蔽。】。最多两个信号事件执行,多个子进程信号被忽略了。- 父进程结束的时候我们要加个循环,防止父进程先与子进程结束,导致捕捉信号执行自定义函数的功能失效。
# include
# include
# include
# include
# include
# include
# include
# include
# include
void sys_err(char* str)
{
perror(str);
exit(1);
}
void do_sig_child(int sig_no)
{
int status;
pid_t pid;
while((pid = waitpid(0,&status,WNOHANG))>0)
{
if(WIFEXITED(status))
{
printf("child %d exit %d \n",pid,WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("child %d exit %d \n",pid,WTERMSIG(status));
}
}
}
int main()
{
int i;
pid_t pid;
//阻塞
sigset_t set,oldset;
sigemptyset(&set);//清空
sigaddset(&set, SIGCHLD);//设置阻塞 SIGINT
sigprocmask(SIG_BLOCK, &set, &oldset);
for ( i=0;i<10;i++)//创建多个进程
{
pid=fork();
if(pid==0)
{
break;
}
else if(pid<0)
{
sys_err("fork");
}
}
if(10==i)// 父进程中注册捕获信号函数
{
if (signal(SIGCHLD, do_sig_child)== SIG_ERR)
{
perror("signal");
exit(1);
}
//解除阻塞
sigprocmask(SIG_UNBLOCK, &set, &oldset);
while(1)//防止父进程先与
{
printf("Parent ID %d\n", getpid());
sleep(100);
}
}
else
{
printf("i am %d child,pid=%d\n",i,getpid());
}
return 0;
}
如有错误欢迎指出…