和文化有关的吉网站建设模板,广州番禺网站制,wordpress 论坛整合,电子商务网站建设的一般流程✨个人主页#xff1a; 熬夜学编程的小林
#x1f497;系列专栏#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】
目录 1、信号处理
2、阻塞信号
2.1、信号其他相关常见概念
2.2、在内核中的表示
2.3、sigset_t
2.4、信号集操作函数
3、完整…✨个人主页 熬夜学编程的小林
系列专栏 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】
目录 1、信号处理
2、阻塞信号
2.1、信号其他相关常见概念
2.2、在内核中的表示
2.3、sigset_t
2.4、信号集操作函数
3、完整代码
3.1、Makefile
3.2、testsig.cc 1、信号处理
补充云服务器之间文件传输
scp 文件名 另外一个云服务器ip:文件路径 # 会需要用到另外服务器的密码 信号的默认处理动作中有两个终止程序区别是什么呢 core : 默认操作是终止进程并转储核心(帮助我们形成debug文件 -- 进程退出的时候的镜像数据)。 term : 默认操作是终止进程 使用对空指针进行解引用或者除零错误做测试 #include iostream
#include unistd.hint main()
{// int* p nullptr;// *p 10;std::cout getpid() std::endl;int a 10;a / 0;return 0;
} 1、直接执行可执行程序不会进行核心转储因为云服务器默认是关闭的。 ulimit -a : 显示当前用户的所有资源限制情况。 2、ulimit -c 文件大小单位为字节 设置核心文件大小 3、再执行可执行程序就会进行核心转储生成core文件(ubuntu(20.04) 和 centos系统方式不一样ubuntu只能core dump一个文件centos以 core pid 的方式生成文件可以core dump多个文件)。 注意ubuntu22.04版本可能默认不会生成core文件
# 将 core 文件的命名模式设置为 core.[PID]普通用户需要使用sudo提权
sudo bash -c echo core.%p /proc/sys/kernel/core_pattern 在 Bash shell 环境中执行一个命令该命令将 /proc/sys/kernel/core_pattern 文件的内容设置 为 core.%p。这里/proc/sys/kernel/core_pattern 是一个系统文件用于控制当程序崩溃时生成的 core 文件的命名和行为。 centos 7 ubuntu 22.04 云服务器为什么要关闭核心转储 1、节省存储空间生成核心转储文件core dump可能会占用大量磁盘空间特别是在服务频繁崩溃或异常终止的情况下。 2、保护用户隐私和数据安全核心转储文件包含了程序崩溃时的内存快照和寄存器状态可能包含敏感信息如用户数据、密钥等。 3、core是协助我们进行debug的文件 --- 事后调试。 通过核心转储文件查看代码在哪里崩溃的
apt install gdb # ubuntu下下载gdbcore-file core文件名 # 查看代码在哪里崩溃 遗留问题 获取子进程status中前面我们只讲解了退出码和退出信号还有一个标记位就是核心转储标志。
代码测试
int Sum(int start, int end)
{int sum 0;for (int i start; i end; i){// sum / 0; // 除零错误sum i;}return sum;
}int main()
{// int total Sum(0, 100);// std::cout total total std::endl;pid_t id fork();if (id 0){// childsleep(1);int total Sum(0, 100);std::cout total total std::endl;exit(1);}// fatherint status 0;int rid waitpid(id, status, 0);if (rid 0){printf(exit code: %d, exit sig: %d, core dump: %d\n,(status 8) 0xFF, status 0x7F, (status 7) 0x1);}return 0;
} 2、阻塞信号
2.1、信号其他相关常见概念 实际执行信号的处理动作称为信号递达(Delivery) --- 默认忽略自定义捕捉信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞 (Block )某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作. 一个信号 是否递达 和 他 有没有未决 有关系 无关 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。 2.2、在内核中的表示
信号在内核中的表示示意图 block表信号屏蔽字表 表示是否对信号进行阻塞。使用位图BitSet来存储若信号被阻塞则相应位置1否则置0。此表用于控制哪些信号当前被阻塞不会被处理。pending表未决位图表 记录当前未决信号即信号已产生但尚未递达的状态。也使用位图来存储若信号存在则相应位置1否则置0。pending表中的数据是判断信号是否存在的唯一依据。当信号产生但未被处理可能因阻塞或其他原因时它会被记录在pending表中。handler表处理函数表 存储信号的处理方法的指针通常是一个函数指针数组。每个信号的处理函数指针都存储在相应的位置当信号递达时会根据handler表找到相应的处理函数并执行。此表不直接用于判断信号是否产生而是定义了信号被递达时应执行的动作。 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。 2.3、sigset_t 从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。 sigset_t - Linux给用户提供的一个用户级的数据类型, 禁止用户直接修改位图 2.4、信号集操作函数
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的
#include signal.h
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismemberconst sigset_t *set, int signo);
int sigemptyset(sigset_t *set); 功能初始化信号集将信号集中的所有信号都设置为未设置状态即清空信号集表示该信号集不包含 任何有效信号)。参数set 指向要初始化的信号集的指针。返回值成功时返回0失败时返回-1并设置errno以指示错误。int sigfillset(sigset_t *set); 功能将信号集中的所有信号都设置为已设置状态即填充信号集表示使其包含所有信号。参数set 指向要填充的信号集的指针。返回值成功时返回0失败时返回-1并设置errno以指示错误。int sigaddset(sigset_t *set, int signo); 功能将指定的信号添加到信号集中。参数set 指向信号集的指针signo 是要添加的信号编号。返回值成功时返回0如果signo无效即不是一个有效的信号编号则返回-1并设置errno以指示错误。int sigdelset(sigset_t *set, int signo); 功能从信号集中删除指定的信号。参数set 指向信号集的指针signo 是要删除的信号编号。返回值成功时返回0如果signo无效则返回-1并设置errno以指示错误。int sigismember(const sigset_t *set, int signo); 功能检查指定的信号是否存在于信号集中。参数set 指向信号集的指针注意这里是const表示该信号集不会被修改signo 是要检查的信号编号。返回值如果signo在信号集中则返回1如果不在则返回0如果signo无效则返回-1并设置errno以指示错误。 注意, 在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
sigprocmask()
sigprocmask - 更改阻塞、解除阻塞或查询进程信号屏蔽字#include signal.hint sigprocmask(int how, const sigset_t *set, sigset_t *oldset);how:指定了如何更改进程的信号屏蔽字。它可以是以下三个常量之一SIG_BLOCK将 set 指向的信号集中的信号添加到当前进程的信号屏蔽字中即阻塞这些信号。SIG_UNBLOCK从当前进程的信号屏蔽字中移除 set 指向的信号集中的信号即解除对这些信号的阻塞。SIG_SETMASK将当前进程的信号屏蔽字设置为 set 指向的信号集忽略 oldset 参数如果它不为 NULL
则仍然会保存旧的屏蔽字。set:是一个指向 sigset_t 类型的指针它包含了要添加或移除的信号集。
如果 how 参数是 SIG_SETMASK则它指定了新的信号屏蔽字。oldset:是一个指向 sigset_t 类型的指针用于保存调用 sigprocmask 之前的信号屏蔽字。
如果 oldset 是 NULL则不保存旧的屏蔽字。返回值成功时sigprocmask 返回 0失败时返回 -1并设置 errno 以指示错误。
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
sigpending()
sigpending - 获取当前进程pending位图#include signal.hint sigpending(sigset_t *set); // 输出型参数set参数是一个指向 sigset_t 类型的指针该函数会将当前进程的挂起信号集复制到 set 指向的位图中。返回值成功时sigpending 返回 0失败时返回 -1并设置 errno 以指示错误。
用到的头文件
#include iostream
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include signal.h
测试对2号信号屏蔽的现象
打印未决表
void PrintPending(sigset_t pending)
{std::cout curr process[ getpid() ]pending: ;for(int signo 31;signo 1;signo--){// 判断signo信号是否在pending中if(sigismember(pending,signo)) {std::cout 1;}else{std::cout 0;}}std::cout \n;
}
主函数
int main()
{// 1.屏蔽2号信号sigset_t block_set,old_set;sigemptyset(block_set); // 清空信号集sigemptyset(old_set);sigaddset(block_set,SIGINT); // 屏蔽2号信号没有修改内核结构// 1.1.进入进程的block表// 阻塞block_set信号集中的信号old_set为输出型参数保存原始信号集sigprocmask(SIG_BLOCK,block_set,old_set); // 修改当前进行的内核表完成对2号信号的屏蔽int cnt 10;while(true){// 2.获取当前信号集sigset_t pending;sigpending(pending); // 输出型参数// 3.打印pending信号集PrintPending(pending);cnt--;// 4.解除对2号信号的屏蔽if(cnt 0){std::cout 解除对2号信号的屏蔽 std::endl;// 解除对old_set信号集的屏蔽原信号集保存到block_set中sigprocmask(SIG_SETMASK,old_set,block_set);}sleep(1);}return 0;
} 信号解除屏蔽之后pending位图也要置零是在递达前还是递达后呢 通过对2号信号自定义捕捉来证明捕捉之后在自定义捕捉函数内部打印pending表如果打印的表中2号信号的比特位为1表示在递达前置零如果为0则在递达后置零。 自定义捕捉方法
// 测试在递达之前清零还是递达之后
void handler(int signo)
{std::cout signo 号信号被递达 std::endl;std::cout -------------------------- std::endl;sigset_t pending;sigpending(pending);PrintPending(pending);std::cout -------------------------- std::endl;
}
主函数
int main()
{// 0.对2号信号自定义捕捉signal(2,handler);// 1.屏蔽2号信号sigset_t block_set,old_set;sigemptyset(block_set); // 清空信号集sigemptyset(old_set);sigaddset(block_set,SIGINT); // 屏蔽2号信号没有修改内核结构// 1.1.进入进程的block表// 阻塞block_set信号集中的信号old_set为输出型参数保存原始信号集sigprocmask(SIG_BLOCK,block_set,old_set); // 修改当前进行的内核表完成对2号信号的屏蔽int cnt 10;while(true){// 2.获取当前信号集sigset_t pending;sigpending(pending); // 输出型参数// 3.打印pending信号集PrintPending(pending);cnt--;// 4.解除对2号信号的屏蔽if(cnt 0){std::cout 解除对2号信号的屏蔽 std::endl;// 解除对old_set信号集的屏蔽原信号集保存到block_set中sigprocmask(SIG_SETMASK,old_set,block_set);}sleep(1);}return 0;
} 3、完整代码
3.1、Makefile
testsig:testsig.ccg -o $ $^ -stdc11 -g
.PHONY:clean
clean:rm -rf testsig
3.2、testsig.cc
#include iostream
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include signal.h// int main()
// {
// int* p nullptr;
// *p 10;
// std::cout getpid() std::endl;
// // int a 10;
// // a / 0;
// return 0;
// }// int Sum(int start, int end)
// {
// int sum 0;
// for (int i start; i end; i)
// {
// sum / 0; // 除零错误
// sum i;
// }
// return sum;
// }// int main()
// {
// // int total Sum(0, 100);
// // std::cout total total std::endl;// pid_t id fork();
// if (id 0)
// {
// // child
// sleep(1);
// int total Sum(0, 100);
// std::cout total total std::endl;
// exit(1);
// }
// // father
// int status 0;
// int rid waitpid(id, status, 0);
// if (rid 0)
// {
// printf(exit code: %d, exit sig: %d, core dump: %d\n,
// (status 8) 0xFF, status 0x7F, (status 7) 0x1);
// }
// return 0;
// }// int main()
// {
// // sigset_t Linux给用户提供的一个用户级的数据类型, 禁止用户直接修改位图
// sigset_t bits; // return 0;
// }void PrintPending(sigset_t pending)
{std::cout curr process[ getpid() ]pending: ;for(int signo 31;signo 1;signo--){// 判断signo信号是否在pending中if(sigismember(pending,signo)) {std::cout 1;}else{std::cout 0;}}std::cout \n;
}// 测试在递达之前清零还是递达之后
void handler(int signo)
{std::cout signo 号信号被递达 std::endl;std::cout -------------------------- std::endl;sigset_t pending;sigpending(pending);PrintPending(pending);std::cout -------------------------- std::endl;
}int main()
{// 0.对2号信号自定义捕捉signal(2,handler); // 自定义捕捉signal(2,SIG_IGN); // 忽略一个信号signal(2,SIG_DFL); // 信号的默认处理动作// 1.屏蔽2号信号sigset_t block_set,old_set;sigemptyset(block_set); // 清空信号集sigemptyset(old_set);sigaddset(block_set,SIGINT); // 屏蔽2号信号没有修改内核结构// 1.1.进入进程的block表// 阻塞block_set信号集中的信号old_set为输出型参数保存原始信号集sigprocmask(SIG_BLOCK,block_set,old_set); // 修改当前进行的内核表完成对2号信号的屏蔽int cnt 10;while(true){// 2.获取当前信号集sigset_t pending;sigpending(pending); // 输出型参数// 3.打印pending信号集PrintPending(pending);cnt--;// 4.解除对2号信号的屏蔽if(cnt 0){std::cout 解除对2号信号的屏蔽 std::endl;// 解除对old_set信号集的屏蔽原信号集保存到block_set中sigprocmask(SIG_SETMASK,old_set,block_set);}sleep(1);}return 0;
}