linux system signal


原文链接: linux system signal

一、信号的概念

  • 信号随时发生,接受信号的进程也可以没有控制权
  • 每个信号名都以SIG开头,包含在
  • 当进程接收到一个信号,他可以对信号采取如下操作之一:
    忽略这个信号
    捕获这个信号,这需要执行一段称为信号处理器的特殊代码
    允许执行信号的默认操作
  • 当进程对发送给它的信号采取措施,叫信号被传送
  • 产生信号和递送信号之间的时间间隔叫做信号为决
  • 进程不能简单的通过判断一个变量,如errno来判断是否出现一个信号

二、捕获信号

  • 当一个进程调用fork的时候,其子进程继承父进程的信号处理方式
    所以信号捕捉函数的地址在子进程中式有意义的
  • 利用函数指针回调函数捕获信号
  • 进程捕捉到信号并对信号进行处理时,进程正在执行的指令序列临
    时中断,它首先执行该信号处理程序中的指令
  • 如果信号处理程序返回(没有调用exit(0)或者abort),则继续执行在
    捕捉到信号时正在执行的正常指令序列
  • 在信号处理程序中,不能判断捕捉到信号时进程正在何处执行

signal 函数(若为命令行,则使用kill向指定进程发送信号)
#include
void (*signal(int signo,void (*func)(int)))(int);
//参数signo是前面表格中的信号名
//参数func式接收到此信号后要调用的函数。该函数有个int
型参数,int代表捕获到的信号值

/signal函数的例子/
void catch_Signal(int Sign)
{

switch (Sign)
{
    case SIGINT:
        printf ("SIGINT Signal\n");
        break;//改变了消息的用途,可以收到CTRL+C后退出进程
    case SIGALRM:
        printf ("HELLO\n");
        exit(0);

}

}

int main()
{

signal(SIGINT,catch_Signal);
signal(SIGALRM,catch_Signal);
pause ();
return 0;

}

三、发送信号
1)kill

  • 使用kill命令
  • 使用kill函数

#include
#include
int kill(pid_t pid,int sig)
//参数pid指定一个要杀死的信号,而sig是要发送的信号

2)raise函数
#include
int raise(int signo);
//kill 函数将信号发送给进程,raise函数允许发信号给自身
//raise(signo)等价于kill(getpid(),signo);

3)alarm函数

  • alarm函数设置一个定时器,当定时器到了就发送SIGALRM信号
    #include
    unsigned int alarm(unsigned int seconds);
    //seconds是计时器时间到后时钟的秒数
    //如果没有设置其他超时,函数返回0,否则返回值为前面安排超时中保留的秒数
    //一个进程只能设置一次超时
    //把second设置为0可以取消前面的超时设置

四、改进捕获信号机制
sigaction函数的功能是检查或修改与指定信号相关联的
处理动作,该函数替代了signal函数
#include
int sigaction (int signo,const struct sigaction *act,

           struct sigaction *oact);

//参数signo是要检测或者修改其具体动作的信号编号
(或同时执行这两种操作)
//如果act指针为空,则要修改其动作
//如果oact指针为空,则系统由oact指针返回该信号的上一个动作
//成功返回0,失败返回-1

struct sigaction{

void (*sa_handle)(int);
void (*sasigaction)(int,siginfo_t *,void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer )(void);

}

/sigaction例子/
void catch_Signal(int Sign)
{

switch (Sign)
{
    case SIGINT:
        printf ("SIGINT Signal\n");
        exit(0);

}

}

int signal1(int signo,void (*func)(int))
{

struct sigaction act,oact;
act.sa_handler = func;//回调函数初始化
sigemptyset(&act.sa_mask);//初始化状态字集合,可以设置未决信号
act.sa_flags = 0;
return sigaction(signo,&act,&oact);

}

int main()
{

signal1(SIGINT,catch_Signal);
pause();
return 0;

}

五、使用自定义信号
/自定义signal函数的例子/
int static status = 0;

void catch_Signal(int Sign)
{

switch (Sign)
{
    case SIGINT:
        printf ("SIGINT Signal\n");
        break;
    case SIGUSR1:
        status = 1;


}

}

int signal1(int signo,void (*func)(int))
{

struct sigaction act,oact;
act.sa_handler = func;//回调函数初始化
sigemptyset(&act.sa_mask);//初始化
act.sa_flags = 0;
return sigaction(signo,&act,&oact);

}
/捕获SIGUSR1信号/
int main()
{

signal1(SIGINT,catch_Signal);
signal1(SIGUSR1,catch_Signal);
printf("pid = %d\n",getpid());
while(1)
{
    if(status = 1);
        printf("SIGUSR1 UP!\n");
    sleep(1);
}
return 0;

}

/发送signal信号/
int main(int argc,char *args[])
{

if(argc > 1)
{
    kill(atoi(args[1],SIGUSR1);
    printf("sent to %d\n",atoi(args[1]))
}

return 0;

}

六、守护进程(daemon)

  • 守护进程是一个后台进程,无须用户输入就能运行
  • 守护进程不能够控制终端,所以任何输入或者输出都需做特殊处理
  • 守护进程执行fork之后就让父进程退出
  • 子进程中调用setsid,取消进程和任何终端的关联
  • 下一步是让根目录成为进程的当前工作目录(因为如果它的当前目录是在一个被安装的
    文件系统上,那么就会妨碍这个文件系统被卸载)
  • 接下来设置umask为0(为了避免守护进程继承的umask收到创建文件和目录操作的干扰,这
    一步是必要的)
  • 最后关闭子进程继承的任何不必要的文件描述符

总结:
1)父进程中执行fork,执行exit退出
2)在子进程中调用setid
3)让根目录'/'成为子进程的工作目录
4)把子进程的umask变为0
5)关闭不需要的文件描述符

setsid函数:pid_t setsid()
//setsid函数创建一个新会话和一个新进程组,然后守护进程成为新会话的领导
//以及新进程组的领导
//setsid还保证新会话没有控制终端
//如果调用进程已经是一个进程组的领导进程,setsid会失败
//setsid调用成功返回新会话id,失败返回-1,并设置errno

chdir函数:int chdir(const char*pathname)
//chdir函数根据参数pathname设置当前工作目录
//chdir函数调用成功返回0,失败返回-1,并设置errno

umask函数:mode_t umask(mode_t mask)
//umask调用把守护今晨的umask设置为0,这样取消了父进程的umask,避免了潜在的
//干扰创建文件和目录

/创建守护进程代码/
int main()
{

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

if (pid > 0)
{
    exit(0);
}
if(pid == 0)
{
    setsid();//脱离控制台
    chdir("/");//非必须,防止mount出错
    umask(0);
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

}

while(1)
    {
        printf("hello\n");
        sleep(1);
    }

return EXIT_SUCCESS;

}

守护进程写入系统日志:
1)openlog函数打开日志:void openlog(const char *ident,int option,int facility);
//参数ident是要向每个消息加入的字符串,典型的情况是程序名称
//参数option是下面一个或多个值的"或"
名称 含义
LOG_CONS 如果系统日志服务器不能用, 写入控制台
LOG_NDELAY 立即打开连接,正常情况下,直到发送第一条消息才打开连接
LOG_PERROR 打印输出到stderr
LOG_PID 每条消息中包含进程PID
//参数facitity指定程序发送消息的类型
名称 含义
LOG_AUTHPRIV 安全授权信息
LOG_CRON 时钟守护进程,cron和at
LOG_DAEMON 其他系统守护进程
LOG_KERN 内核消息
LOG_LPR 打印机子系统
LOG_MAIL 邮件子系统
LOG_USER 默认
2)syslog写入日志:void syslog (int priority,const char * format,...)
//参数priority指定消息重要性
名称 含义
LOG_EMERG 系统不能使用
LOG_ALERT 立即采取措施
LOG_CRIT 紧急事件
LOG_ERR 出错条件
LOG_WARNING 警告条件
LOG_NOTICE 正常但重大事件
LOG_INFO 信息消息
LOG_DEBUG 调试信息
3)closelog关闭日志:void closelog(void);

/syslog日志例子/
syslog(LOG_INFO,"my daemon is OK!");
/严格说,openlog和closelog是可选的,因为函数syslog在首次使用的时候自动打开/

使用信号与守护进程通信
void catch_Signal(int Sign)
{

switch(Sign)
{
    case SIGTERM:
        exit(EXIT_SUCCESS);
}

}

/通过脚本结束守护程序/
#!/bin/sh

WHOAMI=whoami

PID=ps -u $WHOAMI | grep mydaemon | awk '{print} $1'

if(test "$PID"!="") then

kill $PID

fi

/通过脚本开启守护程序/
#!/bin/sh

WHOAMI=whoami

PID=ps -u $WHOAMI | grep abc | awk '{print} $1'

if(test "$PID"="") then

 ./mydaemon

fi

使用FIFO与守护进程通信
/读FIFO/
void readfifo()
{

int len = 0;
char buf[1024];
memset(buf,0,sizeof(buf));
int fd = open ("/home/test/1/fifo",O_RDONLY);
while((len = read (fd,buf,sizeof(buf))>0))
{
    printf("%s",buf);
}
close(fd);
return ;

}

/写FIFO/
void readfifo()
{

int len = 0;
char buf[1024];
memset(buf,0,sizeof(buf));
int fd = open ("/home/test/1/fifo",O_WRONLY);
scanf("%s",buf);
write(fd,buf,sizeof(buf));
close(fd);
return ;

}

如何让后台运行的daemon程序向屏幕打印信息
1 执行mkfifo创建一个管道文件
2 编译daemon,执行程序,关闭当前控制台终端窗口,让程序后台运行
3 打开一个新的终端窗口,通过ps aux找到进程pid
4 执行 #kill -s 2 PID;tty > fifo1

/daemon基础架构/
int signal1(int signo,void (*func)(int))
{

struct sigaction act,oact;
act.sa_handler = func;//回调函数初始化
sigemptyset(&act.sa_mask);//初始化
act.sa_flags = 0;
return sigaction(signo,&act,&oact);

}

void setdaemon()
{

pid_t pid,sid;
pid = fork();
if(pid < 0)
{
    printf("fork failed %s\n",strerror(errno));
    exit(EXIT_SUCCESS);
}
if(pid > 0)
{
    exit(EXIT_SUCCESS);
}
if( (sid = setsid() ) < 0)
{
    printf("setsid failed %s\n",strerror(errno));
    exit(EXIT_SUCCESS);
}

}

void listenfifo()
{

const char * sfifoname = "fifo1";
int len = 0;
char buf[128];
memset(buf,0,sizeof(buf));
int fd = open(sfifoname,O_RDONLY);
if(fd == -1)
{
    printf("open %s failed,%s\n",sfifoname,strerror(errno));
}
len = read (fd,buf,sizeof(buf));
if(len > 0)
{
    if (buf[strlen(buf) - 1] == '\n')
    {
        buf[strlen(buf) - 1] = 0;
    }
    close(STDOUT_FILENO);
    open(buf,O_WRONLY);
}
close(fd);

}

void catch_Signal(int Sign)
{

switch(Sign)
{
    case SIGINT:
            listenfifo();
            break;
    case SIGPIPE:
            ;
            break;
}

}
int main(void)
{

setdaemon();
signal1(SIGINT,catch_Signal);
signal1(SIGPIPE,catch_Signal);//不捕获会退出
while(1)
{
    puts("!!!Hello World!!!");
    sleep(1);
}
return EXIT_SUCCESS;

}

`