TTTTTTZZZZZZ(系统编程---进程间通信,进程信号,C/C++字符串转数字)9

共享内存的原理是两个进程通过页表映射到同一块物理内存上,然后就可以开始通信了,可以手动解除映射关系,也可以等待进程退出,再解除物理内存和进程的映射关系,但是申请好的共享内存的生命周期并不会随着进程的退出而释放,而是要等到手动释放或者重启内核,也就是开关机才可以释放。

插入:C++中的建堆操作

#include <iostream>
#include <vector>
#include <algorithm>   //包含算法头文件
using namespace std;
int main() {
    vector<int> nums = {4, 5, 1, 3, 2};
    // generate heap in the range of numsector
    make_heap(nums.begin(), nums.end());   //建堆
    cout << "initial max value : " << nums.front() << endl;
    // pop max value
    pop_heap(nums.begin(), nums.end());  //删除堆顶元素
    nums.pop_back();
    cout << "after pop, the max vsalue : " << nums.front() << endl;
    // push a new value
    nums.push_back(6);
    push_heap(nums.begin(), nums.end());
    cout << "after push, the max value : " << nums.front() << endl;
    system("pause");
    return 0;
}

============================================================================
消息队列:

消息队列是带类型的队列,每次按照指定的类型来出队列

广义的消息队列;
消息队列也是一个队列,每个元素都带有一个类型
出队列的时候,是按照指定的类型先进先出的
大概模型:
在这里插入图片描述
中间件:和业务无关的基础设施。数据库也是一种中间件

信号量是一个计数器,负责进程间的同步和互斥

这个计数器描述了可用资源的个数。
每次有进程想申请一个可用资源的时候,计数器-1 (P)
每次有进程想要释放资源可用资源的时候,计数器+1 ( V)

如果计数器已经是0了,这时候再有进程想要申请资源的话,会有两种情况,
1.进程会阻塞等待
2.进程放弃申请资源

============================================================================

信号:

在Linux可以通过kill -l这样的命令去查看所有的信号,信号总共有62种

信号的特点:
1.信号有很多种
2.操作系统对于每种不同信号的处理方式是不相同的。
3.每种信号在还没有发生之前,其实操作系统已经之后对于这种信号的应对措施了。

段错误就是内存访问越界,是11号信号。
信号就相当于是神经系统。帮助操作系统来处理进程运行过程中遇到的各种意外

信号的产生方式:

解引用空指针会发生程序崩溃。

int * p = NULL;
cout<<*p<<endl;  //崩溃

int a[10] = { 0 };
cout << a[10] << endl; //这段代码确实是可以对非法内存,进行读的,但是当你进行写操作的时候就会发生错误

在这里插入图片描述
解引用空指针是非法的,但是访问数组的越界下标却是可以的

解引用空指针的结果

1.程序崩溃
2.进程异常终止
3.MMU发现进程虚拟空间和实际物理内存映射关系不匹配,然后给操纵系统返回11号信号,操作系统终止进程,提醒用户段错误

信号产生的方式:
1.键盘

CTRL+ C ==>2
CTRL + Z ==>19
CTRL + \ ==>3

2.硬件

MMU
CPU

3.软件触发中断

管道,读端关闭,尝试写,触发信号中断(SIGPIPE),管道破裂
TCP中一定的条件也会触发SIGPIPE信号

4.系统调用产生

kill()函数

前31个信号叫非实时信号,也叫普通信号,后34 - 64叫做实时信号

============================================================================

插入知识:

//在C语言中字符串转数字的方法有两种
//方法1
char array[] = "100";
int num = atoi(array);
printf("%d\n",num);

//方法2
char array[] = "100";
int num = 0;
sscanf(array,"%d",&num);
printf("%d\n",num);

//C++字符串转数字
//方法1
//首先版包含头文件
#include<sstream>
std::stringstream ss;
std::string str = "100";
int num = 0;
ss<<str;
ss>>num;
std::cout<<num<<std::endl;

//方法2
//stoi是C++11中增加的新的方法,atoi,是将char*转数字,而stoi是将string转数字。
std::string str = "100";
int num = std::stoi(str);
std::cout<<num<<std::endl;

kill 命令是也是一个系统调用函数,kill命令底层调用的就是kill()函数

除了使用ps aux | grep 某某进程名 ==== pidof 某某进程名
两个的效果是完全一样的

============================================================================
信号的处理方式:

1.忽略
2.默认处理信号
3.捕捉信号,通过子定制的行为来处理信号

处理信号的函数signed

//首先这个是函数的说明手册,说signal这个函数会在将第一个参数的信号捕捉到之后,不会报给操作系统处理而是
//按照你传入的第二个参数,来处理,又根据typedef可知,第二个参数是typedef void (*sighandler_t)(int);
//第二个参数必须是一个是一个void类型的函数指针
//然后捕捉到函数之后就会按照你函数中指定的方式去进行处理

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

//代码如下
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 void signedal(int signo)  //函数的参数不用自己传,会自动传进来一个信号的id
  5 {
  6   printf("我是被%d号信号干掉的\n",signo);  //尝试打印信号id
  7 }
  8 int main()
  9 {
 10 
 11   signal(2,signedal);      //捕捉到2号信号进行我交给的函数操作                                                                                                           
 12   while(1)
 13   {
 14     printf("哈哈\n");
 15     sleep(1);
 16   }
 17
 18   return 0;
 19 }
打印结果就是这个样子:
哈哈
哈哈
^C2
哈哈
哈哈
^C2
哈哈
哈哈
哈哈
哈哈
^\退出(吐核)



//再来看一个
// 我们使用一个for循环将所有的进程都进行捕捉,看看进程还能不能退出
  1 #include<stdio.h>  
  2 #include<unistd.h>  
  3 #include<signal.h>  
  4 void signedal(int signo)  
  5 {  
  6   printf("我是被%d号信号干掉的\n",signo);  
  7 }  
  8 int main()  
  9 {  
 10   for(int  i = 0;i <= 64;++i)  //循环捕捉
 11   {  
 12     signal(i,signedal);                                                                                                             
 13   }                  
 14   while(1)
 15   {                                                         
 16     printf("哈哈\n");                                       
 17     sleep(1);                                               
 18   }                                                         
 19                                                             
 20   return 0;                                                 
 21 }       
 其实最后进程还是可以退出的,因为9号进程是无法被捕捉了,要不然也就太容易刀枪不入了吧。          

从上面给两端代码看出一个问题就是,捕捉信号这个函数,好像不会因为说,代码执行过了signal函数, 然后就不能捕捉了,而是是在代码的运行过程中全程都可以捕捉

忽略信号

那操作系统怎么来忽略用户所指定的信号呢?
还是上面的函数,只要把第二个参数改成SIG_IGN就自动忽略了
signal(2,SIG_IGN);

信号捕捉过程示意图:
在这里插入图片描述

1.信号处理函数并不是由用户函数去调用的,根用户代码没有调用关系,信号处理函数是一个单独的执行流,何应乎代码之间没有相互调用的关系,而是由内核处理的

2.信号处理函数,原来的执行流就在等待,而不会继续往下执行,也就是说用户代码,会等待内核将信号处理函数处理完毕之后,再继续执行

C++中的try catch底层就是基于信号来处理的

============================================================================
信号的阻塞:
有的时候信号不会被立刻处理,而是等到时机何再进行处理,

在这里插入图片描述
上面说了,用户的代码,和捕捉信号之后调用的回调函数不属于同一个执行流 。一个是用户的流,一个是内核的流

============================================================================
可重入函数

一个函数如果在多个执行流中被调用,就可能出现问题
一个函数可重入,就意味着在多个执行流中调用是没有问题的
一个函数如果不可重入,在多个执行流中调用就是有问题的
如果过一个函数是用来全局变量/静态变量,不可重入
如果一个函数调用了不可重入函数,也是不可重入的

============================================================================

volatile C语言中的关键字,告诉编译器,这个变量必须每次都从内存中去读取,不能加载到寄存器中,为的就是防止编译器对某些变量的过度优化。

编译器在执行多个执行流的时候是特别容易出问题的

volatile经常使用在多线程程序中,编译器对于这种多执行流的情况时不太会判断的

============================================================================

wait阻塞等待
waitpid阻塞/非阻塞等待
阻塞等待:代码简单,但是效率低
非阻塞等待:代码复杂,但是在进程循环判断等待的时候,可以去干点别的事情,效率高
如何才能实现两者的结合呢?既简单,效率又高,SIGCHLD信号,17号信号
只要有子进程退出,子进程就会自动发送17号信号给父进程,此时父进程进些捕捉17号信号,然后在回调函数中对子进程进行回收,回收完之后,再从内核回到父进程的进程,再继续进行运行父进程中的代码

//这段代码就结合了wait 和waitpid的优点,既简单又高效
  1 #include<stdio.h>                                                                                                                 
  2 #include<unistd.h>
  3 #include<sys/wait.h>
  4 #include<signal.h>
  5 void mysignal(int sig)    //在信号捕捉函数的调用函数中,进行回收子进程
  6 {
  7   printf("%d\n",sig);
  8   wait(NULL);
  9 }
 10 int main ()
 11 {
 12   signal(17,mysignal);
 13   int ret = fork();
 14   if(ret > 0)
 15     while(1)
 16     {
 17       sleep(1);
 18     }
 19   else if(ret == 0)
 20     return 0;
 21   else
 22     perror("fork");
 23   return 0;
 24 }                  

前1-31的信号要是同时来多个有可能只处理一次,34-64就不一样了
未决信号集位图,在多个信号同时到达,有可能只被处理一次。

但是上面的代码存在很大的问题,就是假如同时有很多的子进程退出发送了,好几个17号信号,此时在未决信号集中,只记录一个,也就是只执行一次,此时子进程就不能说是完全被回收干净,此时还是会存在僵尸进程的。

//修改上面的代码
  1 #include<stdio.h>                                                                                                                 
  2 #include<unistd.h>
  3 #include<sys/wait.h>
  4 #include<signal.h>
  5 void mysignal(int sig)    //在信号捕捉函数的调用函数中,进行回收子进程
  6 {
  7   printf("%d\n",sig);
  8   while(1)
  	 {
  	   		int ret = waitpid(-1,NULL,WNOHANG);
  	   		//如果ret > 0已经回收到了一个子进程,返回值就是子进程的pid
  	   		//如果ret == 0 还有子进程在,但是子进程还没有结束
  	   		//如果ret < 0没有子进程
  	   		if(ret > 0)
  	   		{
  	   				//什么都不用做继续执行循环,看还有没有其他的子进程全部回收。
	   		}
	   		else
	   		{
	   				//退出循环
	   				break;
	   		}
  	 }
  9 }
 10 int main ()
 11 {
 12   signal(17,mysignal);
 13   int ret = fork();
 14   if(ret > 0)
 15     while(1)
 16     {
 17       sleep(1);
 18     }
 19   else if(ret == 0)
 20     return 0;
 21   else
 22     perror("fork");
 23   return 0;
 24 }                  

其实上面这些 都是没有用的,代码又臭又长:
其实还有一个更简单的,忽略子进程退出的信号,让子进程自生自灭

//最终代码
  1 #include<stdio.h>  
  2 #include<unistd.h>  
  3 #include<sys/wait.h>  
  4 #include<signal.h>  
  5 int main ()  
  6 {  
  7   signal(17,SIG_IGN);     //一句话代码。直接忽略信号,此时子进程再退出,发现父进程不管它了,操作系统就把子进程的资源释放了,                                                                                                          
  8   int ret = fork();                                                    
  9   if(ret > 0)                                                          
 10     while(1)                                                           
 11     {                                                                  
 12       sleep(1);                                                        
 13     }                                                                  
 14   else if(ret == 0)                                                    
 15     return 0;                                                          
 16   else                                                                 
 17     perror("fork");                                                    
 18   return 0;                                                            
 19 }                          

在 man 3 signal 中可以看到
在这里插入图片描述
直接忽略的第二个参数

推荐书籍:C++《C++ cookbook》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值