1. Signal
Signal is used to notify the process that an asynchronous event has occurred. The kernel can also send signals to the process due to internal events, notifying the process that an event has occurred. Note that signals are only used to notify a process of what events have occurred and do not pass any data to the process.
**Use the kill-l command to view the system-defined signal list
2. Signal generation method
① Send a signal to the front desk through a keyboard combination key (add & after a command) Can be sent to the background to run)
a. The default action of the signal is to terminate the process. The default processing action of SIGQUIT is to terminate the process and Core Dump. (Core dump means that when the process terminates abnormally, you can choose to dump the user space of the process. All memory data is saved to the disk. The file name is usually core. This is called core dump. Usually it means that the program has a BUG. You can use a debugger to check the core file to find out the cause of the error.) By default, core files are not allowed to be generated. Because the core file may contain user passwords, which is unsafe, you can use the ulimit command to change this limit during development and debugging to allow core files to be generated.
Use the command ulimit -c 1024
ulimit command changes the Resource Limit of the Shell process. The PCB of the test process is copied from the Shell process, so it also has the same Resource Limit value as the Shell process. This way Core Dump can be generated
② Send a signal to the process by calling the system function
a. The kill command is implemented by calling the kill function. The kill function can send a specified signal to a specified process. The raise function can send a specified signal to the current process (send a signal to itself).
int kill(pid_t pid, int signo);
int raise( int signo);
all return 0 on success and -1 on error;
abort function causes the current process to receive the SIGABRT signal and terminate abnormally.
void abort(void);
Like the exit function, the abort function will always succeed, so there is no return value.
③ Signals generated by software conditions
a.alarm function and SIGALRM signal
unsigned int alarm(unsigned int seconds); calling the alarm function can set an alarm clock, which is to tell the kernel Send a SIGALRM signal to the current process after senconds seconds. The default action is to terminate the current process. The return value of the function is 0 or the number of seconds remaining in the previously set alarm time.
3. How to handle signals
① Ignore this signal
② Perform the default processing action of the signal, usually terminating the process
③ Capture the signal
IV , Signal delivery and blocking
①, Blocking
The actual execution of the signal processing action is called signal delivery (Delivery), and the state between the signal generation and delivery is called signal pending (Pending). A process can choose to block a signal. The blocked signal will remain in the pending state when it is generated, and the delivery action will not be executed until the process unblocks the signal. Blocking and ignoring are different. As long as the signal is blocked, it will not be delivered, while ignoring is an optional processing action after delivery.
Representation of signals in the kernel
Each signal has two flag bits indicating blocking and pending, as well as a function Pointers represent processing actions. When a signal is generated, the kernel sets the pending flag of the signal in the process control block, and does not clear the flag until the signal is delivered.
If this signal is generated multiple times before the process unblocks a signal, in Linux Regular signals that are generated multiple times before delivery are only counted once, while real-time signals that are generated multiple times before delivery can be placed in a queue in sequence. Each signal has only one bit of pending flag, which is either 0 or 1. It does not record how many times the signal has been generated. The blocking flag is also expressed in this way. (blank is a state, pending indicates presence or absence) Pending and blocking flags can be stored with the same data type sigset_t. sigset_t is called a signal set, and a blocking signal set is also called the signal mask of the current process. ,The "shield" here should be understood as blocking rather than ,ignoring.
②, signal set operation function
#include <signal.h> int sigemptyset(sigset_t *set);//初始化对应的信号集bit位为0 int sigfillset(sigset_t *set);//初始化对象的信号集bit位为1 int sigaddset(sigset_t *set, int signo);//添加有效信号 int sigdelset(sigset_t *set, int signo);//删除有效信号 int sigismember(const sigset_t *set, int signo);//判断一个信号集的有效信号中是否包含某种信号,包含返回1,不包含返回0。
③, sigprocmask
Call the function sigprocmask to read or change the signal mask word (blocking signal set) of the process.
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 0 if successful, -1 if error occurs;
If calling sigprocmask unblocks several current pending signals, then Before sigprocmask returns, at least one of the signals must be delivered.
The meaning of the how parameter
SIG_BLOCK set contains the signals we want to add to the current signal mask,
SIG_UNBLOCK set contains We hope to unblock the signal blocked in the word from the current signal shield.
SIG_SETMASK sets the current signal mask word to the value pointed to by set,
④, sigpending
int sigpending(sigset_t * set);
sigpending reads the pending signal set of the current process and sends it out through the set parameter. If the call is successful, it returns 0, if an error occurs, it returns -1.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use the program to explain
#include<stdio.h> 2 #include<unistd.h> 3 #include<signal.h> 4 void printsigset(sigset_t* sig) 5 { 6 int i=0; 7 for(;i<31;i++) 8 { 9 if(sigismember(sig,i))//判断指定信号是否在目标集合中 10 { 11 printf("1"); 12 } 13 else 14 { 15 printf("0"); 16 } 17 } 18 printf("\n"); 19 } 20 int main() 21 { 22 sigset_t s,p;//定义信号集 23 sigemptyset(&s);//初始化 24 sigemptyset(&p); 25 sigaddset(&s,SIGINT);//设置信号ctrl+C 26 sigprocmask(SIG_BLOCK,&s,NULL);//设置阻塞信号集,阻塞 SIGINT信号 27 while(1) 28 { 29 sigpending(&p);//获取未决信号集 30 printsigset(&p); 31 sleep(1); 32 } 33 return 0; 34 }
结果分析:
程序运行时,每秒钟把各信号的未决状态打印一遍,直到按Ctrl-C将会使SIGINT信号处于未决状态,
五、捕捉信号
a.内核如何实现信号的捕捉
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号,处理过程如下,举例来说明
1. 用户程序注册了SIGQUIT信号的处理函数sighandler。
2. 当前正在执行main函数,这时发生中断或异常切换到内核态。
3. 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
4. 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
5. sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
6. 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了
**(先从用户态―>内核态->返回用户态之前检查有信号递达,返回用户态处理信号->处理完成后再进入内核态->如果没有新的信号递达,返回用户态恢复上下文继续执行)
b.sigaction
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1
将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。
如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
c.pause
int pause(void);
pause函数使调用进程挂起直到有信号递达。如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,
用alarm和pause实现sleep(3)的函数
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<signal.h> 4 void sig_alarm(int signo) 5 { 6 //do nothing 7 } 8 unsigned int my_sleep(unsigned int times) 9 { 10 struct sigaction new ,old; 11 unsigned int unslept=0; 12 new.sa_handler=sig_alarm; 13 sigemptyset(&new.sa_mask); 14 sigemptyset(&old.sa_mask); 15 new.sa_flags=0; 16 sigaction(SIGALRM,&new,&old);//注册信号处理函数 17 alarm(times); //设置闹钟 18 pause(); 19 unslept=alarm(0);//取消闹钟 20 sigaction(SIGALRM,&old,NULL);//恢复默认信号处理动作 21 return unslept; 22 } 23 int main() 24 { 25 while(1) 26 { 27 my_sleep(5); 28 printf("5 senconds pass\n"); 29 } 30 return 0; 31 }
六、可重入函数
当捕捉到信号时,不论进程的主控制流程当前执行到哪儿,都会先跳到信号处理函数中执行,从信号处理函数返回后再继续执行主控制流程。信号处理函数是一个单独的控制流程,因为它和主控制流程是异步的,二者不存在调用和被调用的关系,并且使用不同的堆栈空间。引入了信号处理函数使得一个进程具有多个控制流程,如果这些控制流程访问相同的全局资源(全局变量、硬件资源等),就有可能出现冲突。
以上就是Linux--信号的内容,更多相关内容请关注PHP中文网(www.php.cn)!