光之使者 发表于 2024-7-28 17:39:53

【Linux】volatile | SIGCHLD | 多线程概念

1. volatile

在vscode中,创建signal.c文件
https://i-blog.csdnimg.cn/blog_migrate/ed648971226b6bd88a1981ed0228020a.png 故意在while中没有写代码块,让编译器认为在main中,quit只会被检测
https://i-blog.csdnimg.cn/blog_migrate/9e51a02dc80c5ec64d54ba0b2e79c334.png 运行可实行程序后,当输入 2号信号时,调用自定义方法将quit置为1,跳出while循环
编译器优化

编译器有对应的编译优化级别 -O1 -O2 -O3
https://i-blog.csdnimg.cn/blog_migrate/778501d0f97c88486d1e1a6d93316f6c.png 在makefile中,添加-O2的优化级别
https://i-blog.csdnimg.cn/blog_migrate/828895381a05249ec529d9086da9fd5c.png 再次实行可实行程序时,输入2号信号,只调用了对应的自定义方法,说明进入main中的while循环 无法停止
https://i-blog.csdnimg.cn/blog_migrate/c4291b7232d8a450d246830f4489a523.png
全局变量被加载到内存中
while循环判断实际上是一种盘算,会在CPU去实行的
进行盘算时,将内存中的数据load到CPU中的寄存器上,然后才对quit进行真假判断
内存中有当进步程的代码和数据,CPU中有对应的PC指针去指向
若while循环条件满足,pc指针继承指向while循环的代码
https://i-blog.csdnimg.cn/blog_migrate/65efa6711dc4a3a665d8cd83abaea8e5.png
若while循环条件不满足,则pc指针会向下移动,指向下一条语句,并向后实行
正常来说,每次都要实验数据从内存load到CPU的过程
在main函数中 quit是没有被修改的,只是被检测,编译器发现quit变量没有被修改,就不会重复把数据从内存load到CPU中
因此编译器会优化,只需第一次把数据从内存load到CPU中,后续只必要检测寄存器中的数据即可
https://i-blog.csdnimg.cn/blog_migrate/b217a823dc0dfbf6968b9d264cd13600.png
以是刚开始quit为0,将0传给CPU中,后续输入2号信号后,调用自定义方法,quit变为1,
但是在CPU中依旧quit为0,修改了内存中的quit,那CPU中quit就无法影响内存的quit了
不停使用quit为0,以是while循环无法退出
以是要告诉编辑器,保证每次检测,都要从内存中进行数据读取,不要用寄存器中的数据
为相识决这个题目,使用volatile
https://i-blog.csdnimg.cn/blog_migrate/94a04a722a2b0bdaa77b98abfabbf82d.png 使quit变为volatile修饰的全局变量
volatile作用:杜绝对quit变量进行寄存器级别的优化,保证内存可见性
https://i-blog.csdnimg.cn/blog_migrate/94cfc4ed334537be8502ee5c483ef6ec.png 再次运行可实行程序,输入2号信号,跳出while循环,实行main中的printf打印
2.SIGCHLD信号

子进程在运行时会退出,若父进程不关心子进程退出,子进程就会变成僵尸状态
父进程要使用 wait/waitpid去等待子进程 回收僵尸,获取子进程的退出结果
即父进程进行阻塞式等待(什么都不干,就等待子进程的退出结果)
父进程主动检测--------由于子进程退出了,父进程临时不知道
子进程要退出时,会向父进程发信号 SIGCHLD
父进程对于该信号的处理动作是SIG_DFL 什么都不做
验证SIGCHLD的存在

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
pid_t id;
void handler(int signo)
{
    sleep(5);
    printf("捕捉到一个信号:%d,who:%d\n",signo,getpid());
    //-1代表等待任意一个子进程
    pid_t ret=waitpid(-1,NULL,0);
    if(ret>0)
    {
      printf("wait success,ret:%d,id:%d\n",ret,id);
    }
}
int main()
{
    signal(SIGCHLD,handler);//自定义捕捉
   id=fork();
if(id==0)
{
    //子进程
    int cnt=5;
    while(cnt)
    {
      printf("我是子进程,我的pid是:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
      cnt--;
    }
    exit(1);
}

//父进程
while(1)
{
    sleep(1);
}

    return 0;
}



实现一个自定义方法,当子进程退出时,会向父进程发送信号SIGCHLD
调用对应的自定义方法,打印出对应的信号以及父进程的pid值
https://i-blog.csdnimg.cn/blog_migrate/9fe042142bb6f9839336a75bfd8a57b7.png 运行可实行程序后,who的pid值就是父进程的pid
17号信号就是SIGCHLD
同时通过waitpid返回的pid值与子进程的pid值相同
通过for循环创建出10个子进程,若10个子进程发送信号,处理信号必要一个一个处理,以是当发送一个信号时,可能临时被保存下来,但是父进程只有一个比特位 pending位图保存信号,当再次保存信号时,pending位图再次被置为1
,把前次信号覆盖掉,造成信号丢失,末了处理信号时可能比发送信号的数目少
https://i-blog.csdnimg.cn/blog_migrate/e47fb700525a762fafa07a02f134679f.png
有多少子进程,就回收几次,若没有子进程就退出即可
3. 多线程

多线程概念

1.线程是一个实行分支,实行粒度比进程更细,调理成本更低
2.线程是进程内部的一个实行流
3.线程是CPU调理的基本单元,进程是承担分配体系资源的基本实体
下面将会对于这些概念进行剖析
理解概念

什么是多线程

https://i-blog.csdnimg.cn/blog_migrate/df46b049598255ad8b4e8c78aa37033b.png
创建子进程时,只创建PCB,创建出来的PCB继承指向父进程的地址空间
代码区假设有许多函数存在,让不同的PCB实行不同的函数
相当于在一个进程内部包含多个实行流,指向同一个进程内的不同代码地区
每个PCB都是单独的线程
线程在地址空间内运行,以是该线程属于进程
调理成本低

多个线程之间使用的是同一个地址空间和页表
若为新的进程,则还需再次找到新的地址空间和页表并进行切换
局部性原理

CPU内部存在一个硬件cache
把一部分数据预先加载到缓冲区里,进步整机的服从
如CPU正在访问第100行代码,未来有很大概率访问101行,
以是一旦访问到第100行就把100行附近的数据全部load到内存中大概CPU的cache中
多线程在实行代码和数据时,依旧属于这个进程,CPU里面的cache会缓存各种各样的数据
https://i-blog.csdnimg.cn/blog_migrate/68315e9e506ca9667f9d418c7168d070.png
若进行线程切换,由于都属于同一个进程,cache中缓存的数据是不变的
https://i-blog.csdnimg.cn/blog_migrate/45e4d47613f211260507ba0b1ec9b0ed.png
若进行进程切换,把当前缓存的数据设为失效,cache要重新加载当前的代码和数据
调理成本更低,表现在不用对cache进行切换
什么叫做进程

https://i-blog.csdnimg.cn/blog_migrate/30417429196b14a6ea9cf9b8a77ef2ae.png
task_struct 叫做实行流
进程包含 一大堆的实行流、地址空间、页表以及该进程对应的代码和数据
以是进程是承担分配体系资源的基本实体

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【Linux】volatile | SIGCHLD | 多线程概念