Linux篇:信号

一、信号的概念:
①进程必须识别+能够处理信号,信号没有产生,也要具备处理信号的能力---信号的处理能力属于进程内置功能的一部分
②进程即便是没有收到信号,也能知道哪些信号该怎么处理。
③当进程真的受到了一个具体的信号的时候,进程可能并不会立即处理这个信号,而会在合适的时候处理。
④一个进程必须当信号产生到信号开始被处理,就一定会有时间窗口,要求进程具有临时保存哪些信号已经发生了的能力。

一点五、详谈ctrl c:

1、Ctrl c为什么能够杀掉我们前台进程呢?
Linux中一次登录中,一个终端一般会配上一个bash进程,每一次登录只允许一个进程为前台进程,可允许多个进程是后台进程。而前后台进程的区别,取决于谁来获取键盘输入(获取键盘输入的为前台进程)。
ctrl c本质是被进程解释成为收到了信号(是2号信号,默认动作就是终止自己)。

2、从硬件的角度,谈谈键盘数据是如何输入给内核的?ctrl c又是如何变成信号的?

a.键盘读取本质上是把外设数据拷贝到键盘文件对应的缓冲区中(输入的过程),操作系统可以通过库函数(如read/scanf/fget)通过文件把数据读到对应的进程缓冲区/用户缓冲区中。
b.键盘在硬件上可以给CPU发送硬件中断,每一个中断都有中断号,通过各中断单元向CPU特定针脚发送中断后,CPU会保存相应的中断号,从而完成了键盘向CPU,通知其数据已就绪的任务(显示器,网卡等设备同理)。
c.在操作系统内,都会在操作系统开机启动的时候,形成一张中断向量表,存储直接访问外设方法(主要是磁盘,还有显示器键盘等设备)的地址。其中,中断表的下标是中断号。
CPU中的寄存器保存数据的本质上是对CPU寄存器充放电的过程。

对于Ctrl c这样的组合键呢,OS会判断输入的是数据还是控制,将ctrl c这样的预定义信号转化为2号信号发送给进程。

综上所述,操作系统就是通过不断向外设接收中断,从而处理外设的。而我们学习的信号就是用软件的方式对进程模拟的硬件中断。
(信号是进程之间事件异步通知的一种方式,属于软中断。)

二、信号的产生:

1. 键盘组合键:
ctrl c   发送2号信号。
ctrl \   发送3号信号。
ctrl z   发送19号信号。
不是所有的信号都是可以被signal捕捉的,9和19号无法被捕捉。这两个进程都跟进程的执行有关。

2. kill命令:kill -signo pid

3、系统调用接口: 

设置对特定进程的自定义处理方法(信号编号,自定义动作)(返回函数指针类型)

①修改特定进程对于信号的处理动作:

//mysignal.cc#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>using namespace std;//int: 收到了哪一个信号
void myhandler(int signo)
{cout << "process get a signal: " << signo << endl;// exit(1);
}int main()
{signal(SIGINT, myhandler); // 只需要设置一次,往后都有效!signal(3, myhandler);signal(19, myhandler); // 无法被捕捉 // 信号的产生和我们自己的代码的运行是异步的while(true){cout << "I am a crazy process" << endl;sleep(1);}
}

② 给任意进程发任意信号:

 

//mykill.cc#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>using namespace std;void Usage(string proc)
{cout << "Usage:\n\t" << proc << " signum pid\n\n";
}// mykill signum pid
int main(int argc, char *argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}int signum = stoi(argv[1]);pid_t pid = stoi(argv[2]);int n = kill(pid, signum);if(n == -1){perror("kill");exit(2);}return 0;
}

 ③给调用者发送指定信号:

 

#include <iostream>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>using namespace std;int main()
{int cnt = 5;while(true){cout << "I am a process, pid: " << getpid() << endl;sleep(1);cnt--;if(cnt == 0) raise(2); // 相当于kill(getpid(), sig)}
}

 ④引起一个正常进程进行终止:(跟直接kill -6 pid的效果不一样)

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>using namespace std;int main()
{int cnt = 5;while(true){cout << "I am a process, pid: " << getpid() << endl;sleep(1);cnt--;if(cnt == 0) abort(); // 效果好和kill(getpid(), 6)不一样:不仅发送6号信号,还强行让进程终止。}
}

4、异常:
为什么除0/野指针会让进程崩溃呢?
为什么除0/野指针会给进程发信号呢?

①当前进程在执行时发生除0操作时,CPU中状态寄存器中的溢出标志位由无效改为有效。
进程在被调度期间,CPU寄存器里储存的都是当前进程的上下文。一旦出现异常,对应的状态寄存器由0置1,故该进程是否出异常与进程切换无关,不会影响其他进程。该设计从调度和硬件层面上保证了进程之间的独立性。
任何异常只会影响进程本身,不会波及到操作系统。但操作系统必须要知道该进程出异常,因为CPU是硬件,操作系统是硬件的管理者。故操作系统向进程发信号,使该进程崩溃。

②该进程指向进程地址空间通过其页表访问其代码和数据。
前几篇文章我们谈过,页表是一个软件结构,通过虚拟与物理地址的关系映射找到物理内存。为了提高效率,页表查表的过程并不是由软件完成,而是由一个MMU(内存管理单元)来完成的(MMU在当代芯片当中已经被集成在了CPU内部)。
访问野指针时,虚拟地址到物理地址转换失败,将把转化失败的地址放入CPU中的某寄存器中。CPU内部会出现硬件报错,这也能被OS识别到。
当然,如果进程不崩溃,那它就要一直被调度运行。从而实现在上层给用户更大的宽容度。

//mysignal.cc#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;void handler(int signo)
{cout << "...get a sig, number: " << signo << endl; // 仅起到捕捉打印作用exit(1);
}// 此处我们会观察到信号一直被触发
int main()
{// signal(SIGFPE, hand  ler);signal(SIGSEGV, handler);// cout << "div before" << endl;cout << "point error before" << endl;sleep(5);// int a = 10;// a /= 0; // 异常:Floating point exception(信号8)int *p = nullptr; // 野指针*p = 100; //段错误: Segmentation fault(信号11)// cout << "div after" << endl;cout << "point error after" << endl;sleep(1);return 0;
}

5. 软件条件:异常不只会由硬件产生。

①软件资源不就绪的情况:

#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;void handler(int signo)
{cout << "...get a sig, number: " << signo << endl; // 仅起到捕捉打印作用exit(1);
}// 此处我们会观察到信号一直被触发
int main()
{signal(SIGSEGV, handler);sleep(5);char buffer[1024];int n = 1024;n = read(4, buffer, n); // 4号文件默认不打开printf("n = %d\n", n);perror("read"); // read: Bad file descriptorsleep(1);return 0;
}

②特殊事件:闹钟(返回值为剩余时间)。

#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;void work()
{cout << "print log..." << endl;
}void handler(int signo)
{work();cout << "...get a sig, number: " << signo << endl; // 仅起到捕捉打印作用
}// 此处我们会观察到信号一直被触发
int main()
{signal(SIGALRM, handler);int n = alarm(5); // 闹钟不是异常,只响一次while(1){cout << "proc is running..." << endl;sleep(1);}sleep(1);return 0;
}

③再谈进程等待: 

 

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;// 再谈进程等待
int main()
{pid_t id = fork();if(id == 0){//childint cnt = 500;while(cnt){while(cnt){cout << "i am a child prcess, pid: " << getpid() << " cnt: " << cnt << endl;sleep(1);cnt--; }exit(0);}// fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id){cout << "child quit info, rid: " << rid << " exit code: " << ((status>>8)&0xFF) << "exit signal: " << (status&0x7F) << " core dump: " << ((status>>7)&1) << endl;}}
}
// Makefilemysignal:mysignal.ccg++ -o $@ $^ -g -std=c++11
.PHPNY:clean
clean:rm -f mysignal

虚拟机在进行除零错误的时候,一段程序崩溃,对应的当前目录下会产生一个临时文件xxx.core,而默认云服务器上面的core功能是被关闭的(保证服务重启的功能一直有效,会形成code dump文件来冲击磁盘,影响服务)。打开系统的core domp功能,一旦进程出异常,OS会将进程在内存中的运行信息dump(转储)到进程的当前目录(磁盘)中,形成core.pid文件:核心转储(core dump)。这样做能够直接复现问题之后,直接定位到出错行。这种先运行再生成core-file称为事后调试。

总结:这些都是信号产生的方式,但是无论信号如何发生,最终一定是OS发送给进程的。因为OS是进程的管理者。

三、信号的发送与保存:

1、对于普通信号而言,对于进程而言,自己有还是没有,收到哪一个信号,是给进程的PCB发。

2、OS用比特位的位置来表示是哪个信号:用01来描述信号,用位图来管理信号。
①比特位的内容是0还是1,表示是否收到。
②比特位的位置(第几个),表示信号的编号。
③所谓“发信号”,本质就是OS去修改task_struct的信号位图对应的比特位。

2、信号保存的原因:进程收到信号之后可能不会立即处理这个信号。信号不会被处理,就要有一个时间窗口。

3、信号的范围[1,31],每一种信号都要有自己的一种处理方法。Handler数组中的函数指针指向操作系统提供的默认方法方法。如果用户自己提供了方法,那么,就将用户提供方法的地址填入该数组(signal方法)。

4、阻塞信号:

①常见概念:
 · 实际执行信号的处理动作称为信号递达(Delivery)
 · 信号从产生到递达之间的状态,称为信号未决(Pending)。
 · 进程可以选择阻塞 (Block )某个信号。
 · 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
 · 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

②内核表示:

pending表表示,当前进程是否信收到了信号以及收到了哪些信号。
block表表示,进程信号是否被屏蔽。
hander表表示,每种信号对应的处理关系。
我们通过以上所述的三张表和两张位图,和一个函数指针数组就可以,实现对普通信号的记录、保存相关的管理工作了。

 · 调用函数sigpromask可以读取或更改进程的信号屏蔽字(阻塞信号集)。

③s igset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态。
④信号级操作函数:
#include <signal.h>
int sigemptyset(sigset_t *set); // 初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
int sigfillset(sigset_t *set); // 初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
// 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号
 · sigprocmask: 读取或更改进程的信号屏蔽字 ( 阻塞信号集 )

 ·  sigpending:读取当前进程的未决信号集。

#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;void PrintPending(sigset_t &pending)
{for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){cout << "1";}else{cout << "0";}}cout << "\n\n";
}void handler(int signo)
{cout << "catch a signo: " << signo <<endl;
}int main()
{// 0、对2号信号进行自定义捕捉signal(2, handler);// 1、先对2号信号进行屏蔽——数据预备sigset_t bset, oset; // 在用户区栈上开辟的空间sigemptyset(&bset);sigemptyset(&oset);sigaddset(&bset, 2); // 此处还未将2号信号屏蔽,因为并没有将数据设置进入进程的task_struct// 1.2 调用系统调用,将数据设置进内核sigprocmask(SIG_SETMASK, &bset, &oset); // 此处已将2号信号屏蔽// 2、重复打印当前进程的pending 00000000000000000000000000000000sigset_t pending;int cnt = 0;while (true){// 2.1 获取int n = sigpending(&pending);if (n < 0)continue;// 2.2 打印PrintPending(pending);sleep(1);cnt++;// 2.3 解除阻塞if (cnt == 20){cout << "unblock 2 signo" << endl;sigprocmask(SIG_SETMASK, &oset, nullptr);}}// 3、发送2号 00000000000000000000000000000010return 0;
}
#include <iostream>
#include <unistd.h>
#include <signal.h>using namespace std;void PrintPending(sigset_t &pending)
{for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){cout << "1";}else{cout << "0";}}cout << "\n\n";
}void handler(int signo)
{cout << "catch a signo: " << signo <<endl;
}int main()
{// 4、 思考:如果我们将所以信号全部屏蔽,那么信号不就都不会处理了吗?9和19号信号不仅不可被捕捉,也不可被屏蔽。sigset_t bset, oset;sigemptyset(&bset);sigemptyset(&oset);for(int i = 1; i <= 31; i++){sigaddset(&bset, i); // 此处还未将所有信号屏蔽,因为并没有将数据设置进入进程的task_struct}sigprocmask(SIG_SETMASK, &bset, &oset);sigset_t pending;while(true){// 2.1 获取int n = sigpending(&pending);if (n < 0)continue;// 2.2 打印PrintPending(pending);sleep(1);}return 0;
}

 四、信号的处理:

1、信号的处理方式:
①默认动作。
②忽略。
③自定义动作(信号的捕捉)。

2、信号是什么时候被处理的?
 · 当我们的进程从内核态返回到用户态的时候,进行信号的检测与处理。典型的例子就是调用系统调用(此时操作系统是自动会做身份切换的)。
 · CPU可以响应来自外部的中断,让操作系统执行。它自己也可以在它自己内部直接产生中断,称为int 80:x86英特尔CPU下,从用户态陷入内核态的汇编语句。

3、信号是如何被处理的?(重谈地址空间3)
 · 内核空间映射的是操作系统的代码和数据。
和用户空间一样的是,内核空间也需要内核级页表和操作系统中的代码和数据建立映射。(不过和用户空间不一样的是,内核空间和物理内存中操作系统的代码和数据也可以直接建立映射。)
 · 假设有多个进程,有几个进程就有几份用户级页表---因为进程具有独立性。而内核级页表只有一份。所以每一个进程看到的3~4GB的东西都是一样的。所以进程再怎么切换,3~4GB空间的内容是不变的。
站在进程角度,在调用系统调用方法时,相当于在自己的地址空间里调用该方法,调用后直接返回到自己的地址空间里。
站在操作系统角度,任何一个时刻都有进程执行,用户想执行操作系统的代码就可以随时执行。
操作系统的本质:基于时钟中断的一个死循环。计算机硬件中有一个时钟芯片,每个很短的时间,向计算机发送时钟中断。当操作系统检测到时钟信号到来时,CPU就会执行时钟中断所对应的方法。
 · CPU有一个ecs寄存器(代码段寄存器)。当它最低的两个比特位(低两位权限位)为00时则为内核态,允许访问操作系统的代码和数据;为11时,则为用户态,只能访问用户自己的代码。CPU为用户提供了int 80(陷入内核)方法来更改CPU的工作级别。

上图整个过程进行4次身份切换,1次信号检测。 

4、信号捕捉:

//mysignal.cc#include <iostream>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <signal.h>using namespace std;//问题1: pending位图,什么时候从1->0. 执行信号捕捉方法之前,先清0,在调用
//问题2: 信号被处理的时候,对应的信号也会被添加到block表中,防止信号捕捉被嵌套调用void PrintPending(){sigset_t set;sigpending(&set);for (int signo = 1; signo <= 31; signo++){if (sigismember(&set, signo))cout << "1";elsecout << "0";}cout << "\n";}void handler(int signo){cout << "catch a signal, signal number : " << signo << endl;while (true){PrintPending();sleep(1);}}int main(){struct sigaction act, oact;memset(&act, 0, sizeof(act));memset(&oact, 0, sizeof(oact));sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, 1);sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);act.sa_handler = handler; // SIG_IGN SIG_DFLsigaction(2, &act, &oact);while (true){cout << "I am a process: " << getpid() << endl;sleep(1);}return 0;}

5、可重入函数:

现象:insert函数被main和handler执行流重复进入(重入)了。导致节点丢失,内存泄漏。这被称为不可重入函数,否则为可重入函数。(目前学到的大部分函数都是不可重入的。)

6、volatile关键字:防止编译器过度优化,保持内存的可见性:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <signal.h>using namespace std;volatile int flag = 0;void handler(int signo)
{cout << "catch a signal: " << signo << endl;flag = 1;
}int main()
{signal(2, handler);// 在优化条件下, flag变量可能被直接优化到CPU内的寄存器中while(!flag); // flag 0, !falg 真cout << "process quit normal" << endl;return 0;
}

7、SIGCHID:子进程退出时,会主动向父进程发送SIGCHID17号信号。
 · 子进程在进行等待的时候,我们可以采用基于信号的方式进行等待。
 · 等待的好处:
①获取子进程的退出状态,释放子进程的僵尸。
②虽然不知道父子谁先进行,但是我们清楚,一定是父进程最后退出。
 · 不过还是要调用wait/waitpid这样的接口,父进程必须保证自己是一直在运行的。

#include <iostream>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;// 必须要等待吗?必须要调用wait吗?
// void handler(int signo)
// {
//     sleep(5);
//     pid_t rid;
//     while ((rid = waitpid(-1, nullptr, WNOHANG)) > 0)
//     {
//         cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl;
//     }
// }int main()
{signal(17, SIG_IGN); // SIG_DFL -> action -> IGN// srand(time(nullptr));// signal(17, handler);// 如果我们有10个子进程同时退出呢?// 如果退出一半呢?for (int i = 0; i < 10; i++){pid_t id = fork();if (id == 0){while (true){cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;sleep(5);break;}cout << "child quit!" << endl;exit(0);}// sleep(rand()%5+3);sleep(1);}// fatherwhile (true){cout << "I am father process: " << getpid() << endl;sleep(1);}return 0;
}

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

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

相关文章

猫粮哪个牌子质量好性价比高?分享十款主食冻干猫粮品牌排行榜!

一款好的、健康的主粮对猫整体有很大的提升&#xff0c;主食作为猫的日常饮食&#xff0c;直接关乎着小猫是否能摄入充分的营养&#xff0c;达到最佳的理想状态&#xff0c;因此对于每一位铲屎官来说&#xff0c;主食选得好不好至关重要。面对种类众多的主食&#xff0c;很多人…

c/c++ 结构体、联合体、枚举

结构体 结构体内存对齐规则&#xff1a; 1、结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处 2、其他成员变量要对齐到某个数字&#xff08;对齐数&#xff09;的整数倍的地址处。 对齐数&#xff1a;编译器默认的一个对齐数与该成员变量大小的较小值。 vs 中…

编程实际应用实例:洗车店会员管理系统操作教程

一、前言 洗车店在会员管理有时候需要一卡多用&#xff0c;基本也不需要做卡&#xff0c;直接报手机号或车牌号即可完成电子会员卡录入。 下面以 佳易王洗车店会员管理系统软件为例说明&#xff0c; 软件试用版下载或技术支持可以点击下方的官网卡片 如图&#xff1a;这个卡…

【教程】Autojs脚本实现暂停和超时重启功能的思路和示例代码

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 背景介绍 autojs本身不支持暂停脚本&#xff0c;现有网上大部分最直接的做法就是在每条语句后面添加检查是否暂停。当脚本功能和代码量非常打的时候&#xff0c;每一条语句后面都加检测&#xff0c;未免不太现实。…

RFID技术在物流仓储解决方案中的应用

行业现状 当前市场竞争日益激烈&#xff0c;提高生产效率、降低运营成本对企业至关重要。仓库和物流管理在各行业中广泛应用。建立完善的仓库管理流程&#xff0c;提高仓库周转率&#xff0c;减少资金占用&#xff0c;实现资产变现&#xff0c;降低仓库淘汰成本&#xff0c;是…

c++面经总结

C基础语法 C和c的区别 c中new和delete是对内存分配的运算符&#xff0c;取代了c中的malloc和free 标准c中的字符串类取代了标准c函数库头文件中的字符数组处理函数(c中没有字符串类型). 在c中&#xff0c;允许有相同的函数名&#xff0c;不过他们的参数类型不能完全相同&…

【状态机FSM 序列检测 饮料机_2023.12.1】

同步状态机 概念 同步状态机&#xff08;同一脉冲边沿触发&#xff09;&#xff1a;有限个离散状态及某状之间的转移 异步状态机无法综合 分类 Moore状态机 只和状态有关&#xff0c;与输入无关 Mealy状态机 和状态和输入都有关 Mealy型比Moore型少一个状态 结构 由状态寄…

Tomcat从认识安装到详细使用

文章目录 一.什么是Tomact?二.Tomcat的安装1.下载安装包2.一键下载3.打开Tomcat进行测试4.解决Tomcat中文服务器乱码 三.Tomcat基本使用1.启动与关闭Tomcat2.Tomcat部署项目与浏览器访问项目 四.Tomcat操作中的常见问题1.启动Tomcat后&#xff0c;启动窗口一闪而过&#xff1f…

排序 | 冒泡插入希尔选择堆快排归并计数排序

排序 | 冒泡插入希尔选择堆快排归并计数排序 文章目录 排序 | 冒泡插入希尔选择堆快排归并计数排序冒泡排序插入排序希尔排序选择排序堆排序快速排序--交换排序三数取中快速排序hoare版本快速排序挖坑法快速排序前后指针法 快速排序--非递归实现归并排序归并排序非递归实现非比…

【AutoDL】使用云服务器跑深度学习代码

一、AutoDL租用服务器 1.选用服务器 1.算力市场 租用服务器&#xff0c;选择自己心仪的服务器 2.镜像 可以选择一些基础的镜像&#xff0c;社区镜像里是git上有的环境。 3.上传文件 在文件存储中&#xff0c;选择上传的区&#xff0c;在右边点击上传&#xff0c;选择自己的文…

Dockerfile创建镜像LNMP+WordPress

目录 实验部署 nginx 配置mysql 配置php 实验部署 INMPwordpress nginx 172.111.0.10 docker-nginx mysql 172.111.0.20 docker-mysql php 172.111.0.30 docker-php nginx 关闭防火墙和安全机制在opt目录创建nginx MySQL php目录 cd nginx mysql php vim Dockerfile#声…

Arduino中读取SD卡文本文件数据

1、硬件 2、代码 #include <SD.h> #include <SPI.h> // needed for Arduino versions later than 0018const long _1000msTime 1000; // 1000 milli seconds unsigned long _1000msLastTime;File myFile; int date; int Lastdate; String strDate;//…

YOLOv5独家原创改进:SPPF自研创新 | SPPF与感知大内核卷积UniRepLK结合,大kernel+非膨胀卷积提升感受野

💡💡💡本文自研创新改进:SPPF与感知大内核卷积UniRepLK结合,大kernel+非膨胀卷积,使SPPF增加大kernel,提升感受野,最终提升检测精度 收录 YOLOv5原创自研 https://blog.csdn.net/m0_63774211/category_12511931.html 💡💡💡全网独家首发创新(原创),…

垃圾回收 (GC) 在 .NET Core 中是如何工作的?(二)

接上一篇文章垃圾回收 (GC) 在 .NET Core 中是如何工作的&#xff1f;-CSDN博客 GC 会分配堆段&#xff0c;其中每个段都是一系列连续的内存。 置于堆中的对象归类为 3 个代系之一&#xff1a;0、1 或 2。 代系可确定 GC 尝试在应用不再引用的托管对象上释放内存的频率。 编号…

【亚马逊云科技】使用Vscode Amazon-Q完成GUI界面粉笔脚本开发

本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 前言 亚马逊云科技-Q&#xff0c;可以快速获得紧迫问题的相关答案&#xff0c;解决问题…

生物信息学分析领域领先的特制语言环境NGLess(Next Generation Less)介绍、安装配置和详细使用方法

介绍 NGLess&#xff08;Next Generation Less&#xff09;是一种用于生物信息学分析的领先的领域特定语言&#xff08;DSL&#xff09;。它旨在简化和加速NGS&#xff08;Next Generation Sequencing&#xff09;数据的分析过程。NGLess具有清晰的语法和功能&#xff0c;使用户…

机器翻译:跨越语言边界的智能大使

导言 机器翻译作为人工智能领域的瑰宝&#xff0c;正在以前所未有的速度和精度&#xff0c;为全球沟通拓展新的可能性。本文将深入研究机器翻译的技术原理、应用场景以及对语言交流未来的影响。 1. 简介 机器翻译是一项致力于通过计算机自动将一种语言的文本翻译成另一种语言的…

[Unity]关于Unity接入Appsflyer并且打点支付

首先需要去官方下载Appsflyer的UnityPackage 链接在这afPackage 然后导入 导入完成 引入此段代码 using AppsFlyerSDK; using System.Collections; using System.Collections.Generic; using UnityEngine;public class AppflysManager : MonoBehaviour {public static App…

测试总监给我分享的《接口自动化测试》总结,让我成功的入门接口自动化门槛......

前两天在测试技术交流群里&#xff0c;听了一位字节跳动的测试总监分享的接口自动化测试的内容&#xff0c;对接口自动化更加了解了&#xff0c;也为自己接下来在公司实施接口自动化项目提供了思路。 前言 自动化测试&#xff0c;算是近几年比较火热的一个话题&#xff0c;当…

现代C++ 实现单例模式

传统写法有什么问题 如果你了解过单例模式&#xff0c;双重检查锁定模式&#xff08;Double-Checked Locking Pattern&#xff0c;后文简称DCLP&#xff09;的写法你一定不会陌生&#xff0c;甚至你或许认为它是最正确的代码。 class Singleton { public://获取单例Singleton…