1. pause函数 --> wait for signal
该函数可以造成进程主动挂起,等待信号唤醒。调用该系统调用的进程将处于阻塞状态(主动放弃CPU)直到有信号递达将其唤醒。
int pause(void);
返回值:pause() returns only when a signal was caught and the signal-catching function returned. In this case pause() returns -1, and errno is set to EINTR.
(1)如果信号的默认处理动作是终止进程,则进程终止,pause函数没有机会返回。
(2)如果信号默认是忽略,进程继续处于挂起状态,pause不返回。
(3)如果信号处理动作是捕捉,则调用完信号处理函数后,pause返回-1. errno设置为EINTR,表示“被信号中断”。
(4)pause收到的信号不能被屏蔽,如果被屏蔽则pause就不能被唤醒。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
void catchalarm(int signo)
{
printf("Caught signal: %d\n", signo);
}
int main(int argc, char* argv[])
{
if (argc < 2)
{
printf("Please enter a second.\n");
exit(1);
}
if (signal(SIGALRM, catchalarm) == SIG_ERR)
{
perror("signal");
exit(1);
}
alarm(atoi(argv[1]));
if (pause() == -1 && errno == EINTER)
{
printf("Pause finished..\n");
return 0;
}
alarm(0);
return 1;
}
2. 时序竞态:由于失去CPU资源可能导致本来应该在信号发送递达之前“挂起等待信号”变成“挂起等待信号”在信号发送并递达之后了,导致永远等不到要等的信号。
通过屏蔽信号和解除屏蔽的方法,仍然有可能在“解除屏蔽信号”与挂起等待信号之间失去CPU资源,除非将这两个步骤合并成一个“原子操作”。 --> sigsuspend函数
int sigsuspend(const sigset_t* mask); 挂起等待信号。
对时序要求严格的场合下都应该使用sigsuspend替换pause。
sigsuspend函数调用期间,进程信号屏蔽字由其参数mask指定。
用法:在函数调用之前先把要等待的信号屏蔽,在调用时指定的屏蔽字中将该信号取消屏蔽。可将某个信号从临时信号屏蔽字mask中删除,这样在调用sigsuspend时将解除对该信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值。如果原来对该信号是屏蔽态,sigsuspend函数返回后仍然是屏蔽该信号。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void catchsigalrm(int signo)
{
}
unsigned int my_sleep(int seconds)
{
sigset_t newset, oldset;
sigemptyset(&newset);
sigaddset(&newset, SIGALRM);
// alarm之前先把SIGALRM屏蔽
if (sigprocmask(SIG_BLOCK, &newset, &oldset) < 0)
{
perror("sigprocmask error");
exit(1);
}
// 设置SIGALRM捕捉函数
struct sigaction newsa, oldsa;
newsa.sa_handler = catchsigalrm;
sigemptyset(&newsa.sa_mask);
newsa.sa_flags = 0;
if (sigaction(SIGALRM, &newsa, &oldsa) < 0)
{
perror("sigaction error");
exit(1);
}
alarm(seconds);
sigdelset(&newset, SIGALRM);
// 在sigsuspend执行时解除对SIGALRM的屏蔽
if (sigsuspend(&newset) == -1)
{
printf("wait over.\n");
}
unsigned int ret = alarm(0);
sigprocmask(SIG_SETMASK, &oldset, NULL);
return ret;
}
int main(int argc, char* argv[])
{
if (argc < 2)
{
printf("Enter a second.\n");
exit(1);
}
my_sleep(atoi(argv[1]));
return 0;
}
返回值和pause相同。
3. SIGCHLD信号:子进程运行状态发生改变时,默认动作是忽略
产生条件:子进程终止;子进程接收到SIGSTOP信号停止时;子进程处于停止态,接收到SIGCONT信号唤醒时。
借助SIGCHLD信号回收子进程。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void sys_err(const char* str)
{
perror(str);
exit(1);
}
void catchsigchld(int signalnum)
{
pid_t pid;
int status;
while((pid = waitpid(-1, &status, WNOHANG)) > 0)
{
if (WIFEXITED(status))
printf("Parent process recycle a child:%d, with exit = %d\n", pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
{
printf("Parent process recycle a child:%d, with signal num = %d\n", pid, WTERMSIG(status));
}
}
}
int main(int argc, char* argv[])
{
pid_t pid;
int i = 0;
for (; i < 10; ++i)
{
if ((pid = fork()) < 0)
sys_err("fork error");
if (pid == 0)
break;
}
if (pid == 0)
{
printf("Child process, pid = %d\n", getpid());
exit(i + 1);
}
// 在注册SIGCHLD捕捉函数之前先屏蔽SIGCHLD,防止注册完成之前有子进程结束
sigset_t myset;
sigemptyset(&myset);
if (sigprocmask(SIG_BLOCK, &myset, NULL) < 0)
sys_err("sigprocmask error");
struct sigaction newsa, oldsa;
newsa.sa_handler = catchsigchld;
sigemptyset(&newsa.sa_mask);
newsa.sa_flags = 0;
if (sigaction(SIGCHLD, &newsa, &oldsa) < 0)
sys_err("sigaction error");
// 对SIGCHLD信号解除阻塞
if (sigprocmask(SIG_UNBLOCK, &myset, NULL) < 0)
sys_err("sigprocmask error");
while(1)
{
printf("Parent process, pid = %d\n", getpid());
sleep(1);
}
return 0;
}
注:waitpid函数回收子进程要用while。
4. 信号传参:sigqueue函数对应kill函数,但可向指定进程发送信号的同时携带参数
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval
{
int sival_int;
void* sival_ptr;
};
作用:在向指定进程发送指定信号的同时,可以携带数据。但是如果要传地址,需注意不同进程间虚拟地址空间彼此独立,将当前进程地址传递给另一个进程没有实际意义。有时候需要给本进程发送信号,利用捕捉函数的回调特性。
5. 中断系统调用
系统调用分为两类:慢速系统调用和其它系统调用。
慢速系统调用:可能会使进程永久堵塞的一类。如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行(早期),也可以设定系统调用是否重启,如 read(读管道,套接字,设备时)、write、pause、wait等。
慢速系统调用被中断的相关行为,其实就是pause的行为,例如read函数想中断,信号不能被屏蔽;信号的处理方式必须是捕捉(默认,忽略都不可以);中断后若已经读取了一些内容,则返回读取的长度, 如果没有读到任何内容前被信号中断则返回-1,设置errno为EINTR(表示“被信号中断”);
可修改sa_flags参数来设置被信号中断后系统调用是否重启。SA_RESTART可让系统调用被中断后可以重启。
sa_flags还有很多可选参数,适用于不同情况,例如:捕捉到信号后,在执行捕捉函数期间,不希望自动阻塞该信号(相当于让捕捉函数重入),可将sa_flags设置为SA_NODEFER,除非sa_mask中包含该信号。
6. 进程组操作函数
pid_t getpgrp(void);总是返回调用者的进程组ID;
pid_t getpgid(pid_t pid);返回指定进程的进程组ID, 成功返回进程组ID,失败返回-1;当pid为0时,选用调用进程的pid
int setpgid(pid_t pid, pid_t pgid); 改变进程默认所属的进程组。通常可以用来加入一个现有的进程组或创建一个新进程组,如果pid是0则选用调用进程的pid。
7. 守护进程
Linux中的后台服务进程,通常独立于控制终端并且周期性执行某种任务或等待处理某些发生的事件,一般采用以d结尾的名字;
Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互,不受用户登录、注销的影响,一直在运行,如ftp服务器等。
创建守护进程,最关键的一步是调用setsid函数创建一个新的session,并成为session leader。因为新session会丢弃原来的控制终端。
创建守护进程的步骤:
(1)创建子进程,父进程退出,所有工作在子进程中进行形式上脱离了控制终端
(2)在子进程中创建新会话,setsid()函数,使子进程完全独立出来,脱离控制
(3)改变当前目录为根目录,chdir()函数,防止占用可卸载的文件系统,也可以换成其它路径
(4)重设文件权限掩码,umask()函数,防止继承的文件创建屏蔽字拒绝某些权限
(5)关闭文件描述符,继承的打开文件不会用到,浪费系统资源,无法卸载,可将0、1、2重定向到/dev/null
(6)开始执行守护进程的核心工作
(7)守护进程退出处理程序模型