橘子建站,wordpress用插件注册,wordpress section id,打开一个网站搜索页面跳转js文章目录 信号处理方式#xff08;信号递达#xff09;前后台进程 终端按键产生信号kill系统调用接口向进程发信号阻塞信号sigset_tsigprocmasksigpending内核态与用户态#xff1a;内核空间与用户空间内核如何实现信号的捕捉 1、信号就算没有产生#xff0c;进程也必须识别… 文章目录 信号处理方式信号递达前后台进程 终端按键产生信号kill系统调用接口向进程发信号阻塞信号sigset_tsigprocmasksigpending内核态与用户态内核空间与用户空间内核如何实现信号的捕捉 1、信号就算没有产生进程也必须识别和能够处理信号要具备处理信号的能力信号的处理能力属于进程内置功能的一部分 2、当进程收到一个具体的信号时进程可能并不会立即处理这个信号在合适的时候处理信号
3、当信号产生到信号开始被处理这段时间哪些信号已经产生进程必须知道
信号处理方式信号递达
信号处理方式 1、进程内置了对信号的处理默认动作 2、进程可以忽略信号 3、自定义动作会更改进程内置的默认动作比如信号的捕捉
信号捕捉 会将信号的默认处理方式修改,比如一个信号本来默认处理方式是终止进程并退出进程信号捕捉之后该信号的默认处理方式被修改会出现只捕捉而不退出进程了
signal,修改特定进程对于信号的处理动作的 typedef void (*sighandler_t)(int);sighandler_t signal(int signum, sighandler_t handler);handler这个参数是自定义的动作
前后台进程
关于前后台进程 前台进程能获取键盘输入
后台进程不能获取键盘输入
./myprocess 加了 ,就变成后台进程了
[cxqiZwz9fjj2ssnshikw14avaZ lesson31]$ ll
total 36
-rw-rw-r-- 1 cxq cxq 90 Jul 15 22:41 Makefile
-rwxrwxr-x 1 cxq cxq 26440 Jul 15 15:31 myprocess
-rw-rw-r-- 1 cxq cxq 443 Jul 15 22:41 mysignal.cc
-rw-rw-r-- 1 cxq cxq 0 Jul 14 16:59 test.c
[cxqiZwz9fjj2ssnshikw14avaZ lesson31]$ ./myprocess Linux中一次登陆中一个终端一般会配上一个bash每一个登陆只允讲一个进程是前台进程但是可以允许多个进程是后台进程
终端按键产生信号
ctrl c为什么能够杀掉我们前台进程
ctrl c产生的信号只能发给前台进程 ctrl c本质是被进程解释成为收到了信号2号信号
信号是进程之间事件异步通知的一种方式属于软中断
信号的产生和我们自己的代码的运行时异步的 , 异步代码在运行时信号什么时候产生是不清楚的代码不用管信息什么时候产生代码继续运行就可以了
ctrl c : 2号信号
#includeiostream
#includestdio.h#include signal.h#includeunistd.h
using namespace std ;void myhandler(int signo)
{cout process get a signal: signo endl;// exit(1);
}
int main()
{
//捕捉 2号信号signal(2 , myhandler);//myhandler是函数指针//signal只需要设置一次往后都有效//信号的产生和我们自己的代码的运行时异步的 ,异步代码在运行时信号什么时候产生是不清楚的代码不用管信息什么时候产生代码继续运行就可以了while(true){cout I am a crazy process : getpid() endl;sleep(1);}return 0 ;
}ctrl : 3号信号 不是所有的信号都是可以被signal捕捉的比如:19 暂停进程 9杀死进程
kill系统调用接口向进程发信号
kill给pid进程发sig信号
int kill(pid_t pid, int sig);#includeiostream
#includestdio.h#include signal.h#includeunistd.h#include stdlib.h#includestring
using namespace std ;
void Usage(string proc)
{cout Usage:\n\t proc signum pid\n\n;
}// mykill signum pidint main(int argc ,char* argv[]) //命令行参数
{//告诉用户如何使用if(argc !3){Usage(argv[0]);exit(1);}string signumStr(argv[1]);string pidStr(argv[2]);int signum stoi(signumStr); pid_t pid stoi(pidStr);int n kill(pid,signum); if(n -1){ perror(kill);exit(2);}return 0 ;
}
raise发送一个信号给调用者 int raise(int sig);举例
#includeiostream
#includestdio.h#include signal.h#includeunistd.h#include stdlib.h#includestring
using namespace std ;
void myhandler(int signo)
{cout process get a signal: signo endl;// exit(1);
}
int main()
{int cnt 5 ;signal(2,myhandler); //捕捉2号信号while (true){cout I am a process, pid: getpid() endl;sleep(1);cnt--;if(cnt 0) {raise(2); //等价于 kill(getpid() , 2) }}return 0 ;
}abort 发送6号信号和终止进程 void abort(void);信号产生的方式但是无论信号如何产生最终操作系统发送给进程因为操作系统是进程的管理者
alarm 设置一个闹钟 unsigned int alarm(unsigned int seconds);return val: alarm 函数返回的是之前设置的定时器的剩余时间。如果之前没有设置定时器或者定时器已经到期返回 0。 返回的剩余时间是指在调用新的 alarm 之前定时器还有多少秒才会发送 SIGALRM 信号。
#include iostream#include unistd.h#include signal.husing namespace std;
void myhandler(int signo)
{coutxxxxxxxxxxsignoendl;//理解alarm的返回值int n alarm(3);cout剩余时间nendl;//exit(1);
}int main()
{int n alarm(50);//alarm发送的是14号信号signal(14,myhandler);//myha ndler是一个函数指针while(1){cout proc is running pid :%d getpid() endl;sleep(1);}return 0 ;
}打开系统的core dump 核心转储功能一旦进程出异常操作系统会将进程在内存中的运行信息给转诸到进程的当前目录并形成core.pid文件
ulimit 显示和设置用户可以启动的进程的资源限制
[cxqiZwz9fjj2ssnshikw14avaZ output]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 6943
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
打开系统的core dump 核心转储功能将core file size 设置为10240
[cxqiZwz9fjj2ssnshikw14avaZ output]$ ulimit -c 10240
[cxqiZwz9fjj2ssnshikw14avaZ output]$ ulimit -a
core file size (blocks, -c) 10240
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 6943
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
测试 core dump功能
#include iostream#include unistd.h#include signal.h#include sys/types.h#include sys/wait.h
using namespace std;
int main()
{//测试 core dump功能cout a a endl;pid_t id fork();if(id0){//childint cnt 500;while(cnt){cout i am a child process, pid: getpid() cnt: cnt endl;sleep(1);cnt--;}exit(0);}//father //进程等待int status 0 ;pid_t ridwaitpid(id,status,0);//阻塞等待if(ridid){cout child quit info, rid: rid exit code: ((status8)0xFF) exit signal: (status0x7F) core dump: ((status7)1) endl; // (0000 0000 ... 0001)}return 0 ;
}生成的core.pid文件可以通过gdb直接定位到出错行
阻塞信号
信号常见概念 1、实际执行信号的处理动作称为信号递达Delivery。 例进程内置了对信号处理的默认动作、进程可以忽略信号、还是自定义动作 这些都是信号的处理动作都是信号递达
2、信号从产生到递达之间的状态称为信号未决pending。 信号从产生到递达之前此时信号处于位图中此时信号处于保存状态 这种状态称为信号未决
3、进程可以选择阻塞Block某个信号。这里的阻塞可以理解为屏蔽 默认情况下所有信号都是没有被屏蔽的 进程可以屏蔽某些信号在解除屏蔽之前信号 也不会被操作系统进行递达 如果进程没有收到某一个信号也可以屏蔽这个信号
4、被阻塞的信号产生时将保持在未决状态直到进程解除对此信号的阻塞才执行递达的动作。
一个信息如果被阻塞了屏蔽如果此时用户向进程发送了指定的信号该信号不会被递达直到解除了阻塞 也就是说阻塞了还是可以给进程发送信号只不过信号暂时被屏蔽罢了
5阻塞和忽略是完全不同的只要信号被阻塞就不会递达而忽略是在递达之后的一种处理动作
只要信号被阻塞就不会递达即不会被处理屏蔽和解决屏蔽一定是在递达之前做的忽略的本质是处理信号忽略是信号递达的三种方式之一忽略是在递达之后的一种处理动作阻塞可以理解为未读忽略可以理解为已读不回
block表.比特位的内容是1还是0表明是否屏蔽信号 比特位的位置(第几个)表示信号的编号
pending表 比特位的内容是1还是0表明是否收到信号 比特位的位置(第几个)表示信号的编号 handler表 handler表本质上是一个函数指针数组数组的下标代表某一个信号数组的内容代表该信号递达时的处理动作处理动作包括默认、忽略以及自定义 block、pending和handler这三张表的每一个位置是一一对应的
每个信号都有两个标志位分别表示阻塞block和未决pending还有一个函数指针表示处理动作。信号产生时内核在进程控制块中设置该信号的未决标志直到信号递达才清除该标志。在上图中SIGHUP信号未阻塞也未产生过当它递达时执行默认处理动作。SIGINT信号产生过但正在被阻塞所以暂时不能递达。虽然它的处理动作是忽略但在没有解除阻塞之前不能忽略这个信号因为进程仍有机会在改变处理动作之后再接触阻塞。SIGQUIT信号未产生过但一旦产生SIGQUIT信号该信号将被阻塞它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次POSIX.1允许系统递达该信号一次或多次。Linux是这样实现的普通信号在递达之前产生多次只计一次而实时信号在递达之前产生多次可以依次放在一个队列里这里只讨论普通信号
#include iostream#include unistd.h#include signal.h#include sys/types.h#include sys/wait.h
using namespace std;
int main()
{//理解handler表的SIG_IGNSIG_DFL//signal(2,SIG_IGN);//忽略2号信号signal(2,SIG_DFL);//使用2号信号的默认处理方法while(1){cout hello signal endl;sleep(1);}return 0;
}sigset_t
根据信号在内核中的表示方法每个信号的未决标志只有一个比特位非0即1如果不记录该信号产生了多少次那么阻塞标志也只有一个比特位。因此未决和阻塞标志可以用相同的数据类型sigset_t来存储
在我当前的云服务器中sigset_t类型的定义如下不同操作系统实现sigset_t的方案可能不同
typedef __sigset_t sigset_t;# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct{unsigned long int __val[_SIGSET_NWORDS];} __sigset_t;sigset_t称为信号集这个类型可以表示每个信号的“有效”或“无效”状态。
在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞。在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
阻塞信号集也叫做当前进程的信号屏蔽字Signal Mask这里的“屏蔽”应该理解为阻塞而不是忽略
#include signal.h//sigemptyset、sigfillset、sigaddset和sigdelset函数都是成功返回0出错返回-1。int sigemptyset(sigset_t *set);//初始化set所指向的信号集使其中所有信号的对应bit清零表示该信号集不包含任何有效信号int sigfillset(sigset_t *set);//始化set所指向的信号集使其中所有信号的对应bit置位表示该信号集的有效信号包括系统支持的所有信号。int sigaddset(sigset_t *set, int signum);//在set所指向的信号集中添加某种有效信号。int sigdelset(sigset_t *set, int signum);//在set所指向的信号集中删除某种有效信号。int sigismember(const sigset_t *set, int signum); //判断在set所指向的信号集中是否包含某种信号若包含则返回1不包含则返回0调用失败返回-1。
sigprocmask
sigprocmask 用于读取或更改进程的信号屏蔽字阻塞信号集
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);1、如果oldset是非空指针则读取进程当前的信号屏蔽字通过oldset参数传出。 2、如果set是非空指针则更改进程的信号屏蔽字参数how指示如何更改。 3、如果oldset和set都是非空指针则先将原来的信号屏蔽字备份到oldset里然后根据set和how参数更改信号屏蔽字
how参数的可选值及其含义: 以下三个只能选一个作为how参数
SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号相当于maskmask|setSIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号相当于maskmask|~setSIG_SETMASK 覆盖式的设置当前信号屏蔽字为set所指向的值相当于maskset
return val: sigprocmask函数调用成功返回0出错返回-1。
如果调用sigprocmask解除了对当前若干个未决信号的阻塞则在sigprocmask函数返回前至少将其中一个信号递达
sigpending
sigpending用于读取进程的未决信号集
int sigpending(sigset_t *set);sigpending函数读取当前进程的未决信号集并通过set参数传出。 return val :该函数调用成功返回0出错返回-1。
1、将2号信号进行屏蔽阻塞。2、使用kill命令或组合按键向进程发送2号信号。 3、此时2号信号会一直被阻塞并一直处于pending未决状态。 4、使用sigpending函数获取当前进程的pending信号集进行验证 测试代码
#includeiostream#include signal.h
#includeunistd.h
using namespace std ;void PrintPending(sigset_t pending)
{for(int signo 31 ; signo1 ; signo--){if(sigismember(pending , signo) 1)//判断在set所指向的信号集中是否包含某种信号{//存在cout 1;}else{//不存在cout 0;}}// cout \n\n;//将光标从当前位置向下移动两行coutendl;coutendl;}
int main()
{//将2号信号进行屏蔽阻塞。
// 使用kill命令或组合按键向进程发送2号信号。
// 此时2号信号会一直被阻塞并一直处于pending未决状态。
// 使用sigpending函数获取当前进程的pending信号集进行验证
sigset_t bset,oset ;
sigemptyset(bset);//使bset其中所有信号的对应bit清零
sigemptyset(oset);//使oset其中所有信号的对应bit清零sigaddset(bset,2);//在set所指向的信号集中添加2号信号。
sigprocmask(SIG_SETMASK ,bset, oset);//把bset覆盖式的设置到进程的block位图中//代码走到这里已经屏蔽2号信号了//重复打印当前进程的pendingsigset_t pending ;
int cnt 0 ;
while(true)
{// 2.1 获取int n sigpending(pending);if (n 0)continue;// 2.2 打印PrintPending(pending);sleep(1);// 2.3 解除阻塞cnt ;if(cnt 20){cout unblock 2 signo endl;sigprocmask(SIG_SETMASK ,oset,nullptr);//更改进程的信号屏蔽字,覆盖式的设置当前信号屏蔽字为bset所指向的值}
}return 0 ;
}内核态与用户态
内核态允许访问操作系统的代码和数据是一种权限非常高的状态。用户态只能访问用户自己的代码和数据是一种受监管的普通状态。
当进程从内核态返回到用户态时进行信号的检测和处理
调用系统调用时操作系统是自动会身份切换的 从用户身份变成内核身份或者从内核身份变成用户身份
用户态切换为内核态我们称之为陷入内核。每当我们需要陷入内核的时本质上是因为我们需要执行操作系统的代码比如系统调用函数是由操作系统实现的我们要进行系统调用就必须先由用户态切换为内核态
内核空间与用户空间
每一个进程都有自己的进程地址空间该进程地址空间由内核空间和用户空间组成
用户所写的代码和数据位于用户空间通过用户级页表与物理内存之间建立映射关系。 内核空间存储的实际上是操作系统代码和数据通过内核级页表与物理内存之间建立映射关系 CR3 寄存器存储了页表的起始物理地址这个地址指向了页全局目录的起始位置
用户页表有几份? 有几个进程就有几份用户级页表 , 进程具有独立性
内核页表有几份? 1份
内核级页表是一个全局的页表它用来维护操作系统的代码与进程之间的关系。因此在每个进程的进程地址空间中用户空间是属于当前进程的每个进程看到的代码和数据是完全不同的但内核空间所存放的都是操作系统的代码和数据所有进程看到的都是一样的内容
总结
每一个进程看到的3到4GB的东西都是一样的整个系统中进程再怎么切换3到4GB的空间的内容是不变的
进程视角:我们调用系统中的方法就是在我自己的地址空间中进行执行的。
操作系统视角:任何一个时刻都有有进程执行。我们想执行操作系统的代码就可以随时执行
操作系统的本质:基于时钟中断的一个死循环
计算机硬件中有一个时钟芯片每个很短的时间向计算机发送时钟中断
如何理解进程切换 1、在当前进程的进程地址空间中的内核空间找到操作系统的代码和数据。 2、执行操作系统的代码将当前进程的代码和数据剥离下来并换上另一个进程的代码和数据。 当你访问用户空间时你必须处于用户态当你访问内核空间时你必须处于内核态
内核如何实现信号的捕捉
当我们在执行主控制流程的时候可能因为某些情况而陷入内核当内核处理完毕准备返回用户态时就需要进行信号pending的检查。此时仍处于内核态有权力查看当前进程的pending位图
在查看pending位图时如果发现有未决信号并且该信号没有被阻塞那么此时就需要该信号进行处理。
如果待处理信号的处理动作是默认或者忽略则执行该信号的处理动作后清除对应的pending标志位如果没有新的信号要递达就直接返回用户态从主控制流程中上次被中断的地方继续向下执行即可 但如果待处理信号是自定义捕捉的即该信号的处理动作是由用户提供的那么处理该信号时就需要先返回用户态执行对应的自定义处理动作执行完后再通过特殊的系统调用sigreturn再次陷入内核并清除对应的pending标志位如果没有新的信号要递达就直接返回用户态继续执行主控制流程的代码。 sighandler和main函数使用不同的堆栈空间它们之间不存在调用和被调用的关系是两个独立的控制流程