信号(1.信号概念 2.信号的处理方式 3.信号阶段 用户态,内核态)

news2025/2/26 23:13:12

目录

  • 1.临界资源
  • 2.临界值
  • 3.原子性
  • 4.互斥
  • 5.什么是信号量
  • 6.什么是信号
    • 1.信号概念
    • 2.信号的处理方式
    • 3.信号阶段
      • 1.信号使用前,信号的产生
      • 2.为什么进程会崩溃
      • 3.信号产生中
      • 4.信号产生后

1.临界资源

被多个进程能够看到看到额资源叫做临界资源
如果没有堆临界资源进任何的保护,对于临界资源的访问,双方访问都是乱序的,可能会因为读写交叉导致的各种乱码,废弃数据/访问控制方面的问题

2.临界值

对多个进程而言,访问临界资源的代码叫做临界区,比如二个访问共享内存的那一句代码

3.原子性

我们把一件事情,要么没做,要么做了,没有中间状态,我们叫做原子性

4.互斥

任何时刻,只允许一个进程,访问临界资源,这个我们叫做互斥

5.什么是信号量

信号量的本质是计数器
比如你去电影院看电影,电影院可以给多个人访问,我们叫临界资源,电影院只有100个座位,代表了只能有100张票,,因为多了的话就会的座位发生冲突
那么你怎么证明,放映厅里面特定的座位是你的,那么肯定是我买到了票,这个座位就是我的,那么买票的本质是对放映厅中特定的座位的一种,预定机制,那么你不去也没人坐,理想状态,人可能一样会坐,但是计算机里面的数据不会,那么怎么防止卖票卖多了,只要我们设定了一个值int count = 100,卖到了一张就count–,如果count为零就不卖,等有没有人退票或者电影看完下一场,买票的过程要保证原子性,比如一开始有个进程执行操作,操作到一半,发生进程切换换成另一个进程直接买50张票,买完50张票count-- 50次进程退出,回到最开始的那个进程,他又把count改回了100,那么这样就会因为进程交叉,而出现的数据错误,所以我们要保证原子性

总结
信号量是一个计时器,这个计时器对应的操作是原子的

6.什么是信号

1.信号概念

比如红绿灯,闹钟等等,我们要先能识别信号的能力,比如绿灯行,红灯停,闹钟醒了就起床,所以我们要能识别信号所表达的意思,还有闹钟,我们设置闹钟的时候我们早就知道了信号产生之后要做什么,即便当前信号还没产生,也就是闹钟还没响,而这个是叫做提前知道信号的处理方法,而我们知道信号所表达的意思和提前知道信号的处理方法,具备了这二种我们就有了处理特定信号的能力

那么我们把现实情况带入进程和信号
信号是进程发送得,所以进程要具备处理信号的能力
1.该能力一定是预先早就已经有了
2.进程能够识别对应的信号
3.进程能够处理对应的信号

#include<iostream>
#include<unistd.h>

int main()
{
    while(1)
    {
        sleep(1);
    }
    return 0;
}

我们来运行下面代码
在这里插入图片描述
ctrl + c 把进程终止掉

在这里插入图片描述

./myproc & //把当前进程添加到后台进程

在这里插入图片描述
jobs查看后台进程

在这里插入图片描述
fg 1 查找后台任务,把1放在前台任务,我们在ctrl+c,那么他就没终止了

在这里插入图片描述

在这里插入图片描述
如果你想要关闭后台进程,但是你后悔了那么就ctrl + z 这个时候我们调用jobs命令看到进程属于停止状态,我们bg 加进程号,他就恢复了运行状态了

2.信号的处理方式

因为信号产生时异步,当信号产生的时候,对应的进程可能正在做更重要的事情,我们进程可以暂时不处理这个信号,但是你必须要记住这个信号已经来了,等他完成了手上的事在来处理这个信号,
而信号的处理分分三种处理方式
1.默认动作 你送给你女朋友一个小礼物(处于热恋中),你女朋友表示很高兴
2.忽略 你和你女朋友发生了冷战,那么你再送给他礼物,他忽略你
3.自定义动作 和不同的人有不同的对应方式
而上面这种叫做信号的处理,专业点叫信号的捕捉

3.信号阶段

1.信号使用前,信号的产生

ctrl c ctrl z ctrl d 等等就是产生信号 ctrl + c就是向前台产生2信号
kill -l看信号
在这里插入图片描述
那么我们怎么证明是发送2号信号
在这里插入图片描述
signum,给特定信号设置捕捉动作,第二个参数是函数指针,可以自定义动作的函数指针

#include<iostream>
#include<unistd.h>
#include<signal.h>

using namespace std;

void handler(int signo)
{
    cout<<"我是一个进程,刚刚获取了一个信号: "<< signo <<endl;
}

int main()
{
    signal(SIGINT,handler);

    sleep(3);

    while(true)
    {
        cout<<"我是一个正在运行的进程 "<< getpid() <<endl;
        sleep(1);
    }
    
    return 0;
}

可以看到我输入了ctrl + c,他就发出了信号是二,这里要注意的是,handler这里只是设置了,是SIGINT出信号了,才调用handler函数,如果没有信号了,那么永远都不会使用handler函数
在这里插入图片描述

用户层产生信号的方式,键盘产生,操作系统给进程发送(写入)信号

除了使用键盘发送信号,我们还能通过函数来发送信号

通过kill函数来发送信号

kill有二个参数,第一个是要发送的pid,第二个参数是发送的信号
发送信号失败错误返回-1
mykill.cc

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

using namespace std;


static void Usage(const std::string &proc)
{
    cerr << "Usage:\n\t" << proc << " signo pid" << endl;
}

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;
        exit(2);
    }

    return 0;
}

运行一个不会中断的进程,用自己写的可执行文件,向进程发送9信号,终止掉进程
在这里插入图片描述

2.为什么进程会崩溃

进程崩溃的本质是,进程收到硬件的异常而导致OS向目标进程发送信号,进而导致进程终止的现象!
比如除零错误:cpu内部,状态寄存器,当我们出0的时候,cpu内的状态寄存器会被设置成为,有报错,浮点数越界,cpu的内部寄存器(硬件)会记录错误,os就好识别到cpu内存有报错,识别后就好处理

  1. 谁干的?2. 是什么报错(OS->构建信号)->目标进程发送信号->目标进程在合适的时候->处理信号->终止进程

越界&&野指针: 我们在语言层面使用的地址(指针), 其实都是虚拟地址->物理地址->物理内存->读取对应的数据和代码的,如果虚拟地址有问题,地址转化的工作是由(MMU(硬件)+页表(软件)), 如果转化过程就会引起问题->表现在硬件MMU上->OS发现硬件出现了问题1. 谁干的?2. 是什么报错(OS->构建信号) -> 目标进程发送信号->目标进程在合适的时候->处理信号->终止进程

那么了解了上面,进程报异常一定会终止程序吗?
代码

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

using namespace std;

void handler(int signo)
{
    cout<<"我是一个进程,刚刚获取了一个信号: "<< signo <<endl;
}


int main(int argc ,char* argv[])
{

    for (int sig = 1; sig <= 31; sig++)
    {
        signal(sig, handler);
    }

    int a = 10;
    a/=0;

    return 0;
}

可以看到没有终止进程,因为我们把它捕获了,因为原本的信号处理做法是终止进程,而我们把他捕获了自定义处理了,而我们的处理方式只是打印一串字啊,所以异常没有得到处理,所以就发出错误信号
在这里插入图片描述

3.信号产生中

1.当实际执行信号的处理动作称为信号递达,而这个其实也叫做信号处理,而信号处理,就是前面讲的三点 1.默认处理 2.忽略 3.自定义处理
2.信号从产生到递达之间的状态,称为信号未决(Pending),就是信号还没处理的时候
3.进程可以选择阻塞 (Block )某个信号,如果阻塞了,如果你取消了堵塞了,要不然就一直处在未决状态,无论你怎么发信号都不会做处理

忽略信号和堵塞信号有什么不用?
忽略信号是处理信号的一种,只不过他的做法是忽略它也就是什么都不做
而阻塞时拦截信号,不让信号做抵达

在这里插入图片描述

每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。当信号产生操作系统会在进程控制块上修改控制块的未决标志,比如你是1号信号就把你置1,二号就置1,直到信号递达的时候才会清除比特位
上面图片的1号SIGHUP 里面的block和pending为0说明没有收到处理信号代表默认
上面的图片2号SIGINT 里面的pengdng为1说明了已经收到了2号信号但是因为block为1正在处在堵塞状态,所以你没有实现抵达,除外你把block为1,要pendding一直为1
上面的图片3号SIGQUIT,从来没有产生过,但是如果信号来了,我们一样要拦截你
所以上面是横着看的,先看block再看pending再看handler,如果已经收到了信号,再收到同样的信号,剩下的信号是直接丢弃的,linux是这么实现的,普通信号因为位图也只有一个,当然也有的会记多次,多次实现的,就是一个链表,收到信号把他连起来

int sigemptyset(sigset_t *set);//将比特位图全置为0

int sigfillset(sigset_t *set);//将比特位图全置为1

int sigaddset(sigset_t *set, int signum);//将该set位图,多少号信号置为1

int sigdelset(sigset_t *set, int signum);//将该set位图,多少号信号置为0

int sigismember(const sigset_t *set, int signum);//信号signum是否是set位图中的信号

sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1

下面代码我们来查看pending信号集,如果发送2信号就处理进程打印

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

using namespace std;

void handler(int signo)
{
    cout<<"我是一个进程,刚刚获取了一个信号: "<< signo <<endl;
}


static void Usage(const std::string &proc)
{
    cerr << "Usage:\n\t" << proc << " signo pid" << endl;
}

static void showPending(sigset_t *pendings)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(pendings, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

int main(int argc ,char* argv[])
{
    signal(2,handler);
     // 1. 不断的获取当前进程的pending信号集
    sigset_t pendings;
    while (true)
    {
        // 1.1 清空信号集
        sigemptyset(&pendings);
        // 1.2 获取当前进程(谁调用,获取谁)的pending信号集
        if (sigpending(&pendings) == 0)
        {
            // 1.3 打印一下当前进程的pengding信号集
            showPending(&pendings);
        }
        sleep(1);
    }

    return 0;
}

可以看到前面的都是0,这样说明了是默认状态,是可以处理信号的,也可以看到他处理了我向进程发出的信号2,打印了一串字

在这里插入图片描述

下面代码和前面不同的是,把2号信号添加进堵塞状态

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

using namespace std;

void handler(int signo)
{
    cout<<"我是一个进程,刚刚获取了一个信号: "<< signo <<endl;
}


static void Usage(const std::string &proc)
{
    cerr << "Usage:\n\t" << proc << " signo pid" << endl;
}

static void showPending(sigset_t *pendings)
{
    for (int sig = 1; sig <= 31; 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);
    // sigfillset();
    
    //  添加2号信号到信号屏蔽字中
    sigaddset(&bsig, 2);
     // 2. signal
    // 设置用户级的信号屏蔽字到内核中,让当前进程屏蔽到2号信号
    sigprocmask(SIG_SETMASK, &bsig, &obsig);
    signal(2,handler);
     //  不断的获取当前进程的pending信号集
    sigset_t pendings;
    while (true)
    {
        //  清空信号集
        sigemptyset(&pendings);
        // 1.2 获取当前进程(谁调用,获取谁)的pending信号集
        if (sigpending(&pendings) == 0)
        {
            // 打印一下当前进程的pengding信号集
            showPending(&pendings);
        }
        sleep(1);
    }


    return 0;
}

可以看到一开始的信号集是为0的,我们现在发2信号,因为前面我们把它添加进了堵塞状态,所以可以看到,可以看到,但是不能处理,然后也看到了信号集改成了1
在这里插入图片描述
下面代码是全部信号自定义,再堵塞全部信号,过了20秒后全部结束堵塞状态,并处理信号,且信号集全部变为0

#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

using namespace std;

void handler(int signo)
{
    cout<<"我是一个进程,刚刚获取了一个信号: "<< signo <<endl;
}


static void Usage(const std::string &proc)
{
    cerr << "Usage:\n\t" << proc << " signo pid" << endl;
}

static void showPending(sigset_t *pendings)
{
    for (int sig = 1; sig <= 31; sig++)
    {
        if (sigismember(pendings, sig))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << endl;
}

int main(int argc ,char* argv[])
{
    cout << "pid: " << getpid() << endl;
     // 3. 屏蔽2号信号
    sigset_t bsig, obsig;
    sigemptyset(&bsig);
    sigemptyset(&obsig);
    // sigfillset();
    for (int sig = 1; sig <= 31; sig++)
    {
        // 3.1 添加2号信号到信号屏蔽字中
        sigaddset(&bsig, sig);
        // 2. signal
        signal(sig, handler);
    }
   // 3.2 设置用户级的信号屏蔽字到内核中,让当前进程屏蔽到2号信号
    sigprocmask(SIG_SETMASK, &bsig, &obsig);
   
     // 1. 不断的获取当前进程的pending信号集
    sigset_t pendings;
    int cnt = 0;
    while (true)
    {
        // 1.1 清空信号集
        sigemptyset(&pendings);
        // 1.2 获取当前进程(谁调用,获取谁)的pending信号集
        if (sigpending(&pendings) == 0)
        {
            // 1.3 打印一下当前进程的pengding信号集
            showPending(&pendings);
        }
        sleep(1);
        cnt++;
        if(cnt == 20)
        {
            cout << "解除对所有信号的block...." << endl;
            sigprocmask(SIG_SETMASK, &obsig, nullptr);
        }
    }
    return 0;
}

在这里插入图片描述

4.信号产生后

进程处理信号不是立即处理的,是在合适的时候处理的,是在当前进程从内核态切换成用户态的时候,进行检测和处理
页表分为二种,一种是用户层页表以32位为例1,他只负责3G的空间,还有另一个页表叫做内核页表,他负责的是内核空间,都知道页表是能映射到物理内存的,所以os是在内存里面加载的
在这里插入图片描述

当前进程判断是否具有权力,访问内核页表,他是要进行身份切换,
进程如果是用户态只能访问,用户级页表,只能访问自己的用户页表
进程是内核态那么他就能访问内核级页表和用户级页表 权限更高
那么怎么知道我是内核态还是用户态,cpu有对应的寄存器cr3,有比特位标识当前进程的状态0内核态, 3用户态,当系统调用的时候,时间片到了,进程间切换,当然肯定还有其他的复杂操作

你现在写了一串代码,代码里面有open要有打开的文件,你的代码属于用户态,open的代码那一部分属于内核态
在这里插入图片描述

我们之前想的是open直接返回值,但是都来到内核态了,所以打开后的文件要查看PCB信号,看看信号集看看block有没有被置为1,pending有没有收到信号的处理方法做检查,如果属于属于默认状态,就是block表和pending为0那么就是直接返回,继续处理代码,如果block表和pending都为1那么也直接返回,因为你收到了处理信号,但是你被堵塞了,不给解决,如果block为0,pending为1那么就执行我们的handler表,handler表如果是SIG_IGN,那么就是忽略它,把pending置为0,返回对应处,handler表如果是SIG_DFL,默认处理方法,通常都是终止掉这个进程,并所有代码释放掉,保留PCB,并设为僵尸状态,并把PCB填充成我所收到了信号,比如说2,那么也不需要返回了,还有最后一种情况就是自定义处理方式,这个是属于用户态的,因为是用户提供的方法

在这里插入图片描述

上面的自定义方法不能直接返回到你的代码,因为open的打开的文件还没返回提供返回值,而这个实行自定义方法是由用户态实现的,因为如果是内核态实现的话,你等于有了内核的权限(极高的权限),因为这个是用户写的所以这个自定义方法是某些恶意代码之类的,那么就完蛋了,他会造成了不可逆转的破坏,上面的图可以看到有跟线挡着,上面的是用户态,下面是内核态,我想说的是,你的代码到open的代码,进行了一次用户态转内核态,到了调用自定义方法的时候,进行了内核态转用户态,自定义方法返回,又进行了一次用户态转内核态

上面的那么多话,总结起来过程就是
进程的信号在被合适的时候处理,从内核态返回到用户层的时候->检测->处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/9702.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

技术分享 | AlertManager 源码解析

作者&#xff1a;石蓓蓓 爱可生研发工程师&#xff0c;主要负责爱可生产品云DMP树产品的研发工作。 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。 AlertManager 是处理对应用程序的告警的…

ZIP压缩文件的打开密码和自动加密有什么不同?

ZIP是常用的压缩文件格式&#xff0c;对于重要的内容&#xff0c;我们还可以设置密码&#xff0c;从而达到保护文件内容的目的。 通过WinRAR给ZIP文件设置密码保护&#xff0c;可以设置“打开密码”和“自动加密”&#xff0c;那两者有什么不同呢&#xff1f; 设置打开密码是…

【附源码】Python计算机毕业设计万达影院售票管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

图相似度计算——SIMGnn源码解读

在运行代码的时候&#xff0c;需要首先指定参数&#xff0c;--histogram&#xff0c;表示使用直方图特征 1.数据集 数据集我们使用的是AIDS数据集&#xff0c;为内置的数据集&#xff0c;整个数据集大约700张图&#xff0c;每个图少于10个点&#xff0c;每个点由29维的向量组成…

多视角碰撞,探索 Serverless 企业落地更多可能性丨阿里云用户组厦门站

2022 年 9 月 24 日&#xff0c;阿里云在厦门举办了第 12 场阿里云用户组&#xff08;AUG&#xff09;活动&#xff0c;活动主题为“提效降本&#xff0c;Serverless 助力企业快速落地云原生”&#xff0c;吸引了众多技术从业者及企业管理者到场交流。 2009 年&#xff0c;伯克…

油气田工业控制系统现状

石油石化行业综述 石油石化行业分为上游、中游和下游。其中&#xff0c;上游从事的业务包括原油、天然气 的勘探、开发&#xff0c;中游主要是油气 的存储与运输&#xff0c;下游则涵盖炼油、化工、天然气加工等流程型业务及加油站零售等产品配送、销售型业务。通常情况下&…

常用 numpy 函数(长期更新)

文章目录np.where()np.zeros()np.zeros_like()np.divide()np.linalg.norm()np.uint8()np.clip()np.where() np.where有两种用法 np.where(condition,x,y) 当where内有三个参数时&#xff0c;第一个参数表示条件&#xff0c;当条件成立时where方法返回x&#xff0c;当条件不成…

超强功能WebSSH安装,解决Web远程SSH终端

项目地址&#xff1a;https://github.com/huashengdun/webssh 一个简单的 Web 应用程序&#xff0c;用作 ssh 客户端以连接到您的 ssh 服务器。它是用 Python 编写的&#xff0c;基于 tornado、paramiko 和 xterm.js。 特征&#xff1a; 支持SSH密码认证&#xff0c;包括空密…

Windows系统配置CUDA编程环境

像配置一个简单的可以进行CUDA编程的Windows系统环境&#xff0c;分别需要CUDA以及Visual stdio。 注意&#xff0c;如果是新配置的电脑&#xff0c;一定要先安装visual stdio再安装CUDA&#xff0c;否则后面在VS中创建.cu文件时容易出现找不到模块的情况。 一、安装Visual st…

动态规划--(回文子串,最长回文子序列)

代码随想录day 57 动态规划模块 回文子串,最长回文子序列 文章目录1.leetcode 647. 回文子串1.1 详细思路及解题步骤1.2 Java版代码示例2.leetcode 516. 最长回文子序列2.1 详细思路及解题步骤2.2 Java版代码示例1.leetcode 647. 回文子串 1.1 详细思路及解题步骤 该题用动态规…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java杨佑川音乐播放器908v6

大部分步骤是 1.确定选题 选题的确定需要查阅大量的资料&#xff0c;要搞清楚自己大概想要研究的方向是什么。可以选择自己感兴趣的学科或者强势的学科进行研究&#xff0c;同时要多和毕业指导老师多交流&#xff0c;征求老师的意见和建议&#xff0c;最后确立选题。计算机专…

【K8S系列】第九讲:Kubernetes 之探针

目录 一、探针是什么 二、探针类型 2.1 livenessProbe 2.1.1 容器重启策略 2.2 readinessProbe 2.3 startupProbe 2.4 总结 2.5 探针示例 2.6 配置字段介绍 三、探测机制 3.1 HTTP GET探针 3.2 TCP套接字探针 3.3 Exec探针 Tips 一、探针是什么 探针:是由 kub…

OpenCV众筹了一款ROS2机器人rae,开源、功能强、上手简单。来瞅瞅~

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手君。 在2020年、2021年OpenCV分别在Kickstarter上众筹了两款OAK产品&#xff0c;均筹集…

设计模式——桥接模式

桥接&#xff08;Bridge&#xff09;模式 一、基本思想 当一个类内部具备两种或多种变化维度时&#xff0c;使用桥接模式可以解耦这些变化的维度&#xff0c;使高层代码架构稳定。 将抽象与实现分离&#xff0c;使它们可以独立变化。 用组合关系代替继承关系来实现&#xff0…

运维面试必问的中间件高频面试题

1. redis是单线程还是多线程&#xff1f; 这个问题已经被问过很多次了&#xff0c;从redis4.0开始引入多线程&#xff0c;redis 6.0 中&#xff0c;多线程主要用于网络 I/O 阶段&#xff0c;也就是接收命令和写回结果阶段&#xff0c;而在执行命令阶段&#xff0c;还是由单线程…

综述类论文_Machine Learning for Encrypted Malicious Traffic Detection(重要)

文章目录Machine Learning for Encrypted Malicious Traffic Detection: Approaches, Datasets and Comparative Study摘要存在的问题论文贡献1. 基于机器学习的加密流量检测模型的总体框架1.1 Research Target&#xff08;研究目标&#xff09;1.2 Traffic Dataset Collection…

Allegro给各种形式的板框导弧操作指导

Allegro给各种形式的板框导弧操作指导 Allegro可以给板框导弧,让加工出来的板框更加圆滑,具体操作步骤如下 板框是line形式的 选择Manufacture-Drafting-Fillet命令 在Options里面Radius输出导弧的半径,比如78.74 框选两个线段的部分 完成后的效果如下图 框选4个角落,…

Python基础学习

一、Python基础 1.Python介绍 2.发展史 3.Python 2 or 3? 4.安装 5.Hello World程序 6.变量 7.用户输入 8.模块初识 9. .pyc是个什么鬼&#xff1f; 10.数据类型初识 11.数据运算 12.表达式if ...else语句 13.表达式for 循环 14.break and continue 15.表达式while 循环…

最好的Python入门教材是哪本?这本书当之无愧

1.门槛低 适合编程零基础上手 《Python编程 从入门到实践&#xff08;第二版&#xff09;》的作者埃里克马瑟斯&#xff08;Eric Matthes&#xff09;是一名高中科学和数学老师&#xff0c;现居住在阿拉斯加&#xff0c;在当地讲授 Python 入门课程。他从 5 岁开始就一直在编写…

带你深度解析虚幻引擎4的照明和阴影知识

照明是渲染的重要组成部分。有静态光和动态光&#xff0c;它们往往很重并且需要大量计算。今天就让赞奇云工作站带领小伙伴们来学习一下虚幻引擎4中的光照和阴影的知识。 静态照明 静态光在编辑器中预先计算并保存在光照贴图中。 〇&#xff1a;良好的性能和质量&#xff08…