赞
踩
正文开始!
生活中的信号有哪些呢?
红绿灯,下课铃声,信号枪,烽火台,旗语…
别人教(能够认识这些场景下的信号以及所表示的含义)
我早就知道了信号产生之后我们要做什么!即便当前信号还没有产生!—>我们提前已经知道了这个信号的处理方法!
即便这个信号还没产生,我们已经拥有处理信号的能力!
信号是给进程发送的,进程要具备处理信号的能力!
进程处理信号的能力是程序员给予的!OS帮我们提供!
对于进程来讲,即便这个信号还没产生,我们进程已经具有识别和处理这个信号的能力!
前台进程运行的时候你输入其他的命令Bash是无法给你做出响应的!
./程序名 &
以后台进程去运行,你输入其他的命令是可以继续执行的!
但是打出来的东西很乱!
那么如何终止这个进程呢?
jobs
查看后台进程任务编号就是1
将该进程提至前台运行
fg(front ground) 编号
然后Ctrl+c终止进程即可!
让进城在放置后台运行
bg 编号
用kill -l命令可以察看系统定义的信号列表
每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2
Linux下一共有62个信号!
Linux下的信号被分为了两批
我们常用的计算机都是分时操作系统,所以就着重带大家了解前31个信号即可!
因为信号产生时是异步的,当信号产生的时候,对应的进程可能正在做更重要的事情,我们进程可以暂时不处理这个信号!
所以进程可能不需要立即处理这个信号!—>不代表这个信号不会处理!
但是必须记住这个信号已经来了(有信号来了吗?什么信号?)
进程是如何记住这个信号的,在哪呢?
保存在进程的PCB中的!!
tash_struct{
uint_32 sig;//位图结构,每一位代表一个信号,1表示发生
};
task_strcut是内核的数据结构!--->所以只有操作系统有权利修改task_struct内的数据位图!!
OS是进程的管理者,进程的所有属性的获取和设置,只能由OS来!-->无论信号怎么产生,最终一定是OS帮我们进行信号的设置的!!!
有没有产生?[bit位的内容]
什么信号产生?[bit位的位置]
举个栗子:0000 0010 就代表有信号产生,是二号信号!(默认从右往左数)
键盘产生
谁给进程发送的信号呢?—> 只能是操作系统—>更改PCB中的位图结构,该信号比特位由0置1
通过系统接口完成对进城发送信号的过程
由软件条件产生信号
硬件异常产生信号
#include <iostream> #include <string> #include <stdlib.h> #include <cstring> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; void handler(int signo) { cout << "我是一个进程,刚刚获取了一个信号: " << signo << endl; cout << cnt << endl; } int main() { //这里不是调用hander的方法,这里只是设置了一个回调,让SIGINT(2)产生的时候,刚方法才会被调用 //如果不产生SIGINT,该方法不会被调用! // ctrl + c :本质就是给前台进程产生了2号信号,发送给目标进程,目标进程默认对2号信号的处理是终止自己 //我们更改了对二号信号的处理,设置了用户自定义处理方法 signal(SIGINT, handler); sleep(3); cout << "进程已经设置完了!" << endl; while (true) { cout << "我是一个正在运行中的进程: " << getpid() << endl; sleep(1); } return 0; }
给上面代码设置3号信号的捕捉动作
signal(3, handler);
Ctrl+\是发送三号信号
注意:九号信号就算设置了默认处理函数,也照样可以杀掉该进程!
自己实现kill命令
#include <iostream> #include <string> #include <stdlib.h> #include <cstring> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; static void Usage(const std::string &proc) { cerr << "Usage:\n\t" << proc << " signo pid" << endl; } //我想写一个kill命令 // mykill 9 1234 int main(int argc, char *argv[]) { if (argc != 3) { Usage(argv[0]); exit(1); } if (kill(static_cast<pid_t>(atoi(argv[2])), atoi(argv[1])) == -1) { cerr << "kill" << strerror(errno) << endl; } return 0; }
int main(int argc, char *argv[])
{
signal(2, handler); //没有调用对应的handler方法,仅仅是注册
while (1)
{
sleep(1);
raise(2);
}
return 0;
}
发送的信号是SIGABRT信号
我们把SIGABRT设为默认处理会怎么样呢?
int main(int argc, char *argv[])
{
signal(SIGABRT, handler); //没有调用对应的handler方法,仅仅是注册
while (1)
{
sleep(1);
abort();
}
return 0;
}
所以六号信号是可以被捕捉的,但是还是要终止掉进程的!!!
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
int main(int argc, char *argv[])
{
alarm(1);
for(;;)
{
//是谁在推动操作系统做一系列的动作呢?
//硬件---时钟硬件--给OS发送时钟中断
printf("hello : %d\n",cnt++);//统计一个我们的进程1S cnt++多少次
}
return 0;
}
统计一个我们的进程1s会把cnt++多少次!
int main(int argc, char *argv[])
{
int a=1;
a/=0;
return 0;
}
除零:CPU内部有一个状态寄存器,
当我们除零的时候,CPU内的状态寄存器会被设置成为,有报错:浮点数越界
CPU的内部寄存器(硬件),OS就会识别到CPU内有报错啦–>1.谁干的?
2.是什么报错?
(OS–>构建信号)—>目标进程发送信号–>目标进程在合适的时候–>终止进程
int main(int argc, char *argv[])
{
int* p=nullptr;
*p=1;
return 0;
}
越界&&野指针:我们在语言层面上使用的地址(指针),
其实都是虚拟地址—>物理地址—>物理内存–>读取对应的数据和代码的
如果虚拟地址有问题,地址转化的工作是由(MMU(硬件)+页表(软件)),转化过程就会引起问题,表现在硬件MMU上—>OS发现硬件出现了问题
进程崩溃的本质是该进程收到了异常的信号!
为什么呢?
因为硬件异常,而导致OS向目标进程发送信号,进而导致进程终止的现象!
崩溃了,一定会导致进程终止吗??—>不一定!!
int main(int argc, char *argv[])
{
signal(3,handler);
for(int i=1;i<32;i++)
{
signal(i,handler);
}
sleep(3);
cout<<"进程已经设置完了!"<<endl;
int a=10;
a/=0;
return 0;
}
这是在我带大家了解进程控制时留的小尾巴,因为当时没有办法去讲!
[ Linux ] 进程控制(下)----进程等待与进程程序替换
int main(int argc, char *argv[]) { pid_t id=fork(); if(id==0) { //子进程 int* p=nullptr; *p=10;//野指针问题 exit(1); } //父进程 int status=0; waitpid(id,&status,0); printf("exit code: %d,signo: %d,core dump flag: %d\n", (status>>8)&0xff,status&0x7f,(status>>7)&0x1); return 0; }
man 7 signal
我们可以看到不同的信号有不同的Action,有的是Term直接终止了。。。。
那么Core是什么呢?
ulimit -a
线上生产中,core字段默认是0,也就是关闭的!
打开
ulimit -c 100000
打开core字段后,我们发现core dump标记位直接被设置为1了
并且还多了core.28389这个大文件
28389就是我们引起进程发生错误的id号!
core dump会把进程在运行中,对应得异常上下文数据,core dump到磁盘上,方便调试的!—>并且将status中的core dump标记位置为1!
然后我们进行gdb调试,运行该文件,我们就直接定位到出现异常的那一行了!!!
但是,core dump位一般是关闭的,因为他所占内存大小太大了!
信号在内核中的表示示意图
从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞可以使用想的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的"有效"和"无效"的含义是该信号是否被阻塞,而在未决信号集中"有效"和"无效"
的含义是该信号是否处于未决状态。稍后我来带大家了解信号集的各种操作。阻塞信号集也叫作当前进程的信号屏蔽字(Signal Mask),这里的"屏蔽"应该理解为阻塞而不是忽略。
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 sigismember(const sigset_t *set, int signo);
函数的使用
static void showPending(sigset_t *pendings) { for (int sig = 1; sig < 32; sig++) { if (sigismember(pendings, sig)) { cout << '1'; } else cout << '0'; } cout << endl; } int main(int argc, char *argv[]) { sigset_t bsig,obsig; sigemptyset(&bsig); sigemptyset(&obsig); for(int sig=1;sig<=31;sig++) { //2.给2号信号设置自定义选项 signal(sig,handler); //3.屏蔽二号信号 //3.1添加二号信号到信号屏蔽字中 sigaddset(&bsig,sig); } //3.2设置用户级的信号屏蔽字到内核中,让当前进程屏蔽到二号信号 sigprocmask(SIG_SETMASK,&bsig,&obsig); //1.不断的获取当前进程的pending信号集 sigset_t pendings; int cnt=0; while(true) { //1.1清空信号集 sigemptyset(&pendings); //1.2获取当前进程(谁调用,获取谁)的pendings信号集 if(sigpending(&pendings)==0) { showPending(&pendings); } sleep(1); } return 0; }
while(true)中加入部分代码
while(true) { //1.1清空信号集 sigemptyset(&pendings); //1.2获取当前进程(谁调用,获取谁)的pendings信号集 if(sigpending(&pendings)==0) { showPending(&pendings); } sleep(1); cnt++; if(cnt==10) { cout<<"解除对所有信号的block "<<endl; sigprocmask(SIG_SETMASK,&obsig,nullptr); } }
进程处理信号,不是理解处理的。—>在合适的时候!---->是什么时候呢?
当当前进程从内核态,切换回用户态的时候,进行信号的检测与处理!
进程的信号在被合适的时候处理----从内核态返回到用户态的时候—>检测----处理
如何理解内核态和用户态
进程的生命周期中,会有很多次机会去陷入内核(中断,陷阱,系统调用,异常…),一定会存在很多次的机会进行内核态返回用户态!
对信号处理使用自定义方法,我们执行的是用户代码!!!只能用用户态的身份去执行的!—>因为这部分代码是用户写的!!—>如果写的是一段恶意的代码呢!!!
#include <iostream> #include <signal.h> #include <unistd.h> using namespace std; void handler(int signo) { cout << "获取到一个信号,信号的编号是: " << signo << endl; } int main() { struct sigaction act, oact; act.sa_handler = handler;//自定义方法 // act.sa_handler=SIG_IGN;//忽略方法 // act.sa_handler=SIG_DFL;//默认处理方法 act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(2, &act, &oact); while (true) { cout << "main running" << endl; sleep(1); } return 0; }
当某个信号的处理函数被调用的时候,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么他会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,此处我们不做研究!
void handler(int signo) { cout << "获取到一个信号,信号的编号是: " << signo << endl; sigset_t pending; //增加handler信号的处理时间,永远都会在处理二号信号 while (true) { sigpending(&pending); for (int i = 1; i <= 31; i++) { if (sigismember(&pending, i)) cout << "1"; else cout << "0"; } cout << endl; sleep(1); } } int main() { struct sigaction act, oact; act.sa_handler = handler;//自定义方法 // act.sa_handler=SIG_IGN;//忽略方法 // act.sa_handler=SIG_DFL;//默认处理方法 act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(2, &act, &oact); while (true) { cout << "main running" << endl; sleep(1); } return 0; }
(本章完!)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。