Linux系统编程——理解系统内核中的信号捕获
目录
一、sigaction()
使用
信号捕捉技巧
二、可重入函数
三、volatile关键字
四、SIGCHLD信号
在信号这一篇中我们已经学习到了一种信号捕捉的调用接口:signal(),为了深入理解操作系统内核中的信号捕获机制,我们今天再来看一个接口:sigaction()
一、sigaction()
sigaction
一个系统调用,用于修改和/或查询信号的处理方式。它提供了对信号处理函数更精细的控制,相较于旧的 signal
函数来说更为强大和灵活。
1、int signum:该参数需要传入指定的进程信号,表示要捕捉的信号
2、const struct sigaction *act:该参数与函数同名,是一个结构体指针如图
1、void (*sa_handler)(int);,此成员的含义其实就是 自定义处理信号的函数指针;
2、void (*sa_sigcation)(int, siginfo_t *, void *);此成员也是一个函数指针. 但是这个函数的意义是用来 处理实时信号的, 不考虑分析. (siginfo_t 是表示实时信号的结构体)
3、sigset_t sa_mask;, 此成员是一个信号集, 这个信号集有什么用呢?我们在使用时解释
4、int sa_flags;, 此成员包含着系统提供的一些选项, 本篇文章使用中都设置为0
5、void (*sa_restorer)(void);, 很明显 此成员也是一个函数指针. 但我们暂时不考虑他的意义.也就是说我们暂时将该接口的第二个参数简单理解为一个结构体指针,并且结构体里有一个成员是用来自定义处理信号的。即该参数的作用就是将指定信号的处理动作改为传入的struct sigaction的内容。
3、struct sigaction *oldact,第三个参数类似sigprocmask()接口中的第三个参数一样,都是输出型参数,且其作用是获取 此次修改信号 struct sigaction之前的原本的 struct sigaction ,如果传入的是空指针,则不获取。
使用
我们实现一个捕捉2号信号的程序,然后让他按照我们自定义的处理动作执行,且将自定义的处理动作设置为死循环,也就是让进程在收到2号信号后一直执行该信号的处理动作。
#include<iostream>
#include<unistd.h>
#include<signal.h>
using std::cout;
using std::endl;void handler(int signum)
{cout<<"I catch a signal::"<<signum<<endl;sigset_t pending;while(true){sigpending(&pending);int sig=1;for(sig=1;sig<=31;sig++){//利用死循环打印未决信号集if(sigismember(&pending,sig)){cout<<"1";}else{cout<<"0";}}cout<<endl;sleep(1);}
}
int main()
{struct sigaction act,oact;//实例化出两个结构体对象作为参数act.sa_handler=handler;//初始化自定义处理动作act.sa_flags=0;//先设置为0sigemptyset(&act.sa_mask);//初始化sigaction(2,&act,&oact);//捕捉2号信号while(true){cout<<"I am a process!My pid::"<<getpid()<<endl;sleep(1);}return 0;
}
可以看到在我们第一次发送了2号信号后,其打印出来的未决信号集全是0,这是因为在没有捕捉到2号信号前,该进程的未决信号集全为0,捕捉之后,第二个位置应该为1,然后开始处理自定义动作前将1又置为0,表示开始处理自定义动作,此时就一直处于死循环中,即一直在执行自定义动作。
当我们第二次发送2号信号时,它的未决信号集的对应位置就变为了1,后面再发送2号信号时,该信号都会被拦截下来导致后面的2号信号一直处于未决状态。但是发送其他信号进程又会处理其他信号。
那么我们要是想要在处理2号信号的同时还要将其他信号拦截呢?这时候就与 sa_mask 相关了,顾名思义了就是信号屏蔽字。
struct sigaction结构体的sa_mask 成员的意义是, 添加进程在处理捕捉到的信号时对其他信号的阻塞. 如果需要添加对其他信号的阻塞, 那么就可以继续在 sa_mask 中添加其他信号.
不过, 这样做有什么意义呢?
这样做可以 防止用户自定义处理信号时, 嵌套式的发送其他信号并捕捉处理.
如果 用户的自定义处理信号方法内部, 还会发送其他信号, 并且用户还对其进行了捕捉. 那么 信号的处理就无止尽了. 这种情况是不允许发生的.
所以 可以通过使用 sa_mask 来进行对其他信号的拦截阻塞.
信号捕捉技巧
为了避免捕捉不同的信号并做处理时,编写不同的处理函数太过于麻烦,我们可以考虑通过传入相同的函数指针实现对不同信号的不同处理。
当我们定义完指定信号的处理函数之后, 我们可以再定义一个 handlerAll(int signo)
函数, 并使用 switch 语句, 将不同的 signo 分别处理.
此时, 我们在使用 signal()
或者 sigaction()
捕捉信号时, 就只需要统一传入 handlerAll
的函数指针就可以了.
这是一种 解耦技巧
二、可重入函数
可重入函数(Reentrant Function)是指可以在多线程环境中安全使用的函数,即这个函数可以被多个线程同时调用而不会导致数据错误或不一致。
下面用个例子解释一下
一个进程中, 存在一个全局的单链表结构. 并且此时需要执行一个节点的头插操作:
此时需要让该结点的指向下一个节点的指针指向头节点,再将自己变为头节点
node1->next = head;
head = node1;
如果在刚执行完第一步之后, 进程因为硬件中断或者其他原因 陷入内核了
.
陷入内核之后需要回到用户态继续执行, 切换回用户态时 进程会检测未决信号, 如果此时刚好存在一个信号未决, 且此信号自定义处理.并且, 自定义处理函数中 也存在一个新节点头插操作:
此时又会执行node2节点的头插 ,执行完后node2节点暂时就成为了新的头节点
接着进程返回用户态去执行剩下的代码,即 head=node1 ,
导致的结果就是node2节点最终找不到了,这样就造成了 内存泄漏
是因为 单链表的头插函数, 被不同的控制流程调用了, 并且是在第一次调用还没返回时就再次进入该函数, 这个行为称为 重入
而 像例子中这个单链表头插函数, 访问的是一个全局链表, 所以可能因为重入而造成数据错乱, 这样的函数 被称为 不可重入函数, 即此函数不能重入, 重入可能会发生错误
反之, 如果一个函数即使重入, 也不会发生任何错误(一般之访问函数自己的局部变量、参数), 这样的函数就可以被称为 可重入函数. 因为每个函数自己的局部变量是独立于此次函数调用的, 再多少次调用相同的函数, 也不会对之前调用函数的局部变量有什么影响.
如果一个函数符合以下条件之一,则称为不可重入函数
- 调用了malloc和free,因为 malloc 也是用全局链表来管理堆的。
- 调用了标准I/O库函数, 标准I/O库的很多实现都以不可重入的方式使用全局数据结构
三、volatile关键字
在之前学习C/C++的时候,我们就已经接触过这个关键字了,它的作用是防止编译器对该关键字修饰的变量的优化,确保每次访问这个变量时都直接从内存中读取,而不是使用可能存在的寄存器中的缓存值。这是因为在某些情况下,变量的值可能会被外部因素改变,如硬件中断、多线程环境下的其他线程等。
接下来我们用一个例子分析一下该关键字
下面的程序是先定义一个全局变量flag,以该全局变量作为触发条件,当为0时,一直处于死循环状态。当为1时程序正常结束。在main()中不对flag做修改,只有在捕获到2号信号的时候,在自定义的处理函数中才会对flag做出修改。
#include <stdio.h>
#include <signal.h>int flags = 0;void handler(int signo) {printf("获取到一个信号,信号的编号是: %d\n", signo);flags = 1;printf("我修改了flags: 0->1\n");
}int main() {signal(2, handler);while (!flags);// 未接收到信号时, flags 为 0, !flags 恒为真, 所以会死循环printf("此进程正常退出\n");return 0;
}
可以看到在发送了2号信号后程序正常结束。
- 虽然 2信号的自定义处理函数 会对flags作出修改, 但是这个函数的执行是未知的. 即 不确定进程是否会收到2信号 进而执行此函数.
- 那么对编译器来说, 就有可能对 flags 做出优化.
- 我们知道, 进程再判断数据时, 是CPU在访问数据, 而CPU访问数据时 会将数据从内存中拿到寄存器中. 然后再根据寄存器中的数据进行处理.
- 在此例中, 就是 while(!flags); 判断时, CPU会从内存中拿出数据进行判断. 当flags从0变为1时, 是内存中的数据发生了变化, CPU也会从内存中拿到新的数据进行判断
- 而 此例中编译器可以确定一定会执行的代码中, flags是不会被修改的. 那么 编译器就可能针对flags做出优化:
- 由于编译器认为进程不会修改 flags, 那么在 while(!flags); 判断时, CPU读取到flags为0 并存放在寄存器中之后, 为了节省资源 在之后的判断中 CPU 不会再从内存中读取数据, 而是直接根据寄存器中存储的数据进行判断.
- 这就会造成 即使处理信号时将 flags 改为了1, 在进行 while(!flags);判断时, CPU依旧会只根据寄存器中存储的0 来进程判断, 这就会造成 进程不会正常退出
我们可以在编译是使用 -02 选项让编译器做出这样的优化
可以看到即使是flag改成了1,程序依然不会停止
接着我们使用关键字修饰这个变量 volatile int flag=0 ,再查看结果
可以看到已经没有优化了。
四、SIGCHLD信号
我们之前讲到过,在子进程退出的时候,是需要让父进程接收退出信息的,否则子进程会进入僵尸状态,所以我们介绍了有关于进程等待的函数,让父进程主动去询问子进程是否退出,事实上,子进程退出的时候是会通知父进程的,只不过父进程会忽略而已。
在子进程退出的时候,子进程会向父进程发送一个信号,即 SIGCHID
下面我们在父进程中捕捉这个信号看看情况:
#include<iostream>
#include<cstdlib>
#include<signal.h>
#include<unistd.h>
using std::cout;
using std::endl;
using std::cerr;
void handler(int signum)
{cout<<"Child process has exited,mypid::"<<getpid()<<"Signal num::"<<signum<<endl;
}
int main()
{signal(SIGCHLD,handler);pid_t id=fork();if(id<0){cerr<<"fork error!"<<endl;exit(1);}else if(id==0){while(true){cout<<"I am child process,mypid::"<<getpid()<<endl;sleep(2);}exit(0);}while(true){cout<<"I am parent process!my pid::"<<getpid()<<endl;sleep(2);}return 0;}
17号信号即 SIGCHID信号 ,且默认动作是Ign(忽略)
但是对于该信号,内容中说明 子进程暂停或终止,即在子进程暂停或终止的时候都会发送该信号给父进程。我们做个测试,首先需要知道暂停信号是19号,继续信号是18号信号
可以看到无论是暂停、继续、还是终止子进程的时候,其都会向父进程发送该信号。
那么我们知道了这个信号又有什么用处呢?
在介绍进程等待时 提到过,waitpid()接口会等待子进程退出, 而等待的动作是主动去询问子进程是否退出.
现在我们清楚了子进程在退出的时候会发送SIGCHID信号给父进程,那我们让父进程可以通过捕捉该信号去等待子进程。
#include<iostream>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<signal.h>
using std::cout;
using std::endl;
using std::cerr;
void Childprofree(int signo)
{assert(signo==SIGCHLD);pid_t id=waitpid(-1,nullptr,0);if(id>0){cout<<"Waiting success!pid::"<<getpid()<<"child process id::"<<id<<endl;}}int main()
{signal(SIGCHLD,Childprofree);pid_t id=fork();if(id<0){cout<<"Perror fork!"<<endl;exit(0);}else if(id==0){while(true){cout<<"I am child process!my pid::"<<getpid()<<endl;sleep(2);}exit(0);}while(true){cout<<"I am parent process!my pid::"<<getpid()<<endl;sleep(2);}return 0;
}
上面的程序只针对单进程的情况,如果是多个进程的情况下就会有一些问题
我们利用循环创建10个子进程,这10个子进程在打印完十次之后自动退出。
#include<iostream>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<signal.h>
using std::cout;
using std::endl;
using std::cerr;
void Childprofree(int signo)
{assert(signo==SIGCHLD);pid_t id=waitpid(-1,nullptr,0);if(id>0){cout<<"Waiting success!pid::"<<getpid()<<"child process id::"<<id<<endl;}}int main()
{signal(SIGCHLD,Childprofree);for(int i=0;i<10;i++){pid_t id=fork();if(id<0){cout<<"Perror fork!"<<endl;exit(0);}else if(id==0){int cnt=10;while(cnt){cout<<"I am child process!my pid::"<<getpid()<<"cnt::"<<cnt--<<endl;sleep(2);}cout<<"Child process being Z!"<<endl;exit(0);}}while(true){cout<<"I am parent process!my pid::"<<getpid()<<endl;sleep(2);}return 0;
}
我们在右边窗口使用 while :;do ps ajx|head -1&&ps ajx|grep mydwait|grep -v grep;sleep 2;done命令循环查看进程情况
运行结果如图
可以看到等到子进程推出的时候,按道理是所有的子进程都退出都被父进程回收掉,但是只有左边红色框里这几个进程退出了,右边红色框里还有几个没有被回收掉,一直处于僵尸状态。
在Linux中,每个进程都有一个信号集,用来表示该进程当前接收到的、尚未处理的信号。这个集合有一个重要特点:它是基于信号类型的,而不是基于信号的数量。这意味着对于同一类型的信号(例如多个
SIGCHLD
),操作系统不会为每个信号单独排队,而是只会记录该类型信号至少发生过一次。换句话说,如果多个SIGCHLD
信号几乎同时到达,操作系统会将它们合并成一个信号,并只传递给父进程一次。因此,如果大量的子进程几乎在同一时间结束,父进程可能只接收到一个
SIGCHLD
信号,而实际上有多个子进程已经终止。这就导致了父进程可能没有机会处理所有终止的子进程,从而留下僵尸进程。
那么怎么修改处理信号的方式呢?我们将回收子进程的部分设置为 利用死循环回收,在没有子进程的情况下跳出循环。
一旦有收到子进程的退出信号后,这个外部的函数就会进入死循环,不断等待释放需要退出的子进程,直到没有子进程需要释放了就退出。事实上这个改动就跟收到信号没有关系了,单纯利用循环不断等待需要被释放的子进程。
void freeChild(int signo) {assert(signo == SIGCHLD);while(true) {pid_t id = waitpid(-1, nullptr, 0);if (id > 0) {cout << "父进程等待子进程成功, child pid: " << id << endl;}else {cout << "等待结束" << endl;break;}}
}
可以看到最后所有的进程都退出了。
但是新的问题又出现了,一旦有子进程不退出的话,父进程就不会再运行了,因为我们设置的waitpid()的第三个参数为0是阻塞式等待,所以会一直处在外部的自定义处理函数中,不会回到main()函数
观察下面的情况,我们让一部分子进程循环5次后退出,一部分循环30次后退出
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
using std::cout;
using std::endl;void freeChild(int signo) {assert(signo == SIGCHLD);while (true) {pid_t id = waitpid(-1, nullptr, 0);if (id > 0) {cout << "父进程等待子进程成功, child pid: " << id << endl;}else {cout << "等待结束" << endl;break;}}
}int main() {signal(SIGCHLD, freeChild);for (int i = 0; i < 10; i++) {pid_t id = fork();if (id == 0) {// 子进程int cnt = 0;if(i < 6)cnt = 5; // 前6个子进程 5就退出elsecnt = 30; // 后4个子进程 30退出while (cnt) {cout << "我是子进程, pid: " << getpid() << ", cnt: " << cnt-- << endl;sleep(1);}cout << "子进程退出, 进入僵尸状态" << endl;exit(0);}}// 父进程while (true) {cout << "我是父进程, pid: " << getpid() << endl;sleep(1);}return 0;
}
可以看到在所有进程退出之前父进程代码并没有运行,我们只需要将 waitpid()的第三个参数改为 WNOHANG 就可以了表示 非阻塞式等待。
下面是最终版代码
#include<iostream>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<signal.h>
using std::cout;
using std::endl;
using std::cerr;
void Childprofree(int signo)
{assert(signo==SIGCHLD);while(true){pid_t id=waitpid(-1,nullptr,WNOHANG);if(id>0){cout<<"Waiting success!pid::"<<getpid()<<"child process id::"<<id<<endl;}else{cout<<"Wating end!"<<endl;break;}}}int main()
{signal(SIGCHLD,Childprofree);for(int i=0;i<10;i++){pid_t id=fork();if(id<0){cout<<"Perror fork!"<<endl;exit(0);}else if(id==0){int cnt = 0;if(i < 6)cnt = 5; elsecnt = 30; while(cnt){cout<<"I am child process!my pid::"<<getpid()<<"cnt::"<<cnt--<<endl;sleep(2);}cout<<"Child process being Z!"<<endl;exit(0);}}while(true){cout<<"I am parent process!my pid::"<<getpid()<<endl;sleep(2);}return 0;
}
相关文章:

Linux系统编程——理解系统内核中的信号捕获
目录 一、sigaction() 使用 信号捕捉技巧 二、可重入函数 三、volatile关键字 四、SIGCHLD信号 在信号这一篇中我们已经学习到了一种信号捕捉的调用接口:signal(),为了深入理解操作系统内核中的信号捕获机制,我们今天再来看一个接口:si…...

《Java 与 OpenAI 协同:开启智能编程新范式》
在当今科技飞速发展的时代,人工智能已成为推动各领域创新变革的核心力量。OpenAI 作为人工智能领域的领军者,其开发的一系列强大模型,如 GPT 系列,为自然语言处理等诸多任务带来了前所未有的突破。而 Java,作为一种广泛…...

基于Python大数据的电影可视化分析系统
标题:基于 Python 大数据的电影可视化分析系统 内容:1.摘要 本文介绍了一个基于 Python 大数据的电影可视化分析系统。该系统通过收集和分析大量电影数据,提供了对电影市场的深入洞察。文章首先介绍了系统的背景和目的,然后详细描述了系统的架构和功能。…...

【杂谈】-为什么Python是AI的首选语言
为什么Python是AI的首选语言 文章目录 为什么Python是AI的首选语言1、为何 Python 引领人工智能发展1.1 可用性和生态系统1.2 用户群和用例1.3 效率辅助 2、AI项目对Python开发人员的要求3、如何开启你的 AI 学习之旅 人工智能的广泛应用正在软件工程领域引发范式转变。Python凭…...

(高可用版本)Kubeadm+Containerd+keepalived部署高可用k8s(v1.28.2)集群
KubeadmContainerdkeepalived部署高可用k8s(v1.28.2)集群 一.环境准备,二.容器运行时Containerd安装请参照前文。KubeadmContainerd部署k8s(v1.28.2)集群(非高可用版)-CSDN博客 文章目录 KubeadmContainerdkeepalived部署高可用k8s(v1.28.2)集…...

单片机:实现自动关机电路(附带源码)
单片机实现自动关机电路 在许多嵌入式系统或便携式设备中,自动关机功能非常重要,尤其是在电池供电的设备中,防止设备长时间开启以节省电能。自动关机电路的基本功能是检测设备是否处于待机状态,若一定时间内未收到用户操作信号或…...

【YashanDB知识库】ycm-YashanDB列表有数据库显示故障排除步骤
本文内容来自YashanDB官网,原文内容请见 https://www.yashandb.com/newsinfo/7802959.html?templateId1718516 数据库状态 正常 异常 1、查看告警列表 例如:告警显示实例无法连接,一般是数据库实例服务掉了,需要尽快联系系统…...

高级的SQL查询技巧有哪些?
成长路上不孤单😊😊😊😊😊😊 【14后😊///C爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于高级SQL查询技巧方面的相关内容…...

使用 UniApp 在微信小程序中实现 SSE 流式响应
概述 服务端发送事件(Server-Sent Events, SSE)是一种允许服务器向客户端推送实时更新的技术。SSE 提供了一种单向的通信通道,服务器可以持续地向客户端发送数据,而不需要客户端频繁发起请求。这对于需要实时更新的应用场景非常有…...

transformer用作分类任务
系列博客目录 文章目录 系列博客目录1、在手写数字图像这个数据集上进行分类1. 数据准备2. 将图像转化为适合Transformer的输入3. 位置编码4. Transformer编码器5. 池化操作6. 分类头7. 训练8. 评估总结流程:相关模型: 1、在手写数字图像这个数据集上进行…...

【枚举】假币问题
题目描述: 有12枚硬币。其中有11枚真币和1枚假币。假币和真币重量不同,但不知道假币比真币轻还是重。现在,用一架天平称了这些币三次,告诉你称的结果,请你找出假币并且确定假币是轻是重(数据保证一定能找出…...

easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
需求:页面点击导出,先按照页面条件去数据库查询,然后将查询到的数据导出。 问题:由于查询特别耗时,所以点击之后页面会看上去没有反应 方案1:就在点击之后在页面增加了一个进度条,等待后端查询…...

Java模拟Mqtt客户端连接Mqtt Broker
Java模拟Mqtt客户端基本流程 引入Paho MQTT客户端库 <dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.mqttv5.client</artifactId><version>1.2.5</version> </dependency>设置mqtt配置数据 …...

【电商搜索】文档的信息论生成聚类
【电商搜索】文档的信息论生成聚类 目录 文章目录 【电商搜索】文档的信息论生成聚类目录文章信息概览研究背景技术挑战如何破局技术应用主要相关工作与参考文献后续优化方向 后记 文章信息 https://arxiv.org/pdf/2412.13534 概览 本文提出了一种基于信息论的生成聚类&#…...

在福昕(pdf)阅读器中导航到上次阅读页面的方法
文章目录 在福昕(pdf)阅读器中导航到上次阅读页面的方法概述笔记用书签的方法来导航用导航按钮的方法来导航 备注END 在福昕(pdf)阅读器中导航到上次阅读页面的方法 概述 喜欢用福昕(pdf)阅读器来看pdf文件。 但是有个小问题困扰了我好久。 e.g. 300页的pdf看了一半ÿ…...

基于Springboot的数字科技风险报告管理系统
博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了多年的设计程序开发,开发过上千套设计程序,没有什么华丽的语言,只有实…...

【最后203篇系列】001 - 2024回顾
说明 最早在CSDN上写文章有两个目的: 1 自己梳理知识,以备日后查用2 曾经从别人的文章中得到过帮助,所以也希望能给人帮助 所以在这个过程中,我的文章基本上完全是原创,也非常强调落地与工程化。在不断写作的过程中…...

量子退火与机器学习(1):少量数据求解未知QUBO矩阵,以少见多
文章目录 前言ー、复习QUBO:中药配伍的复杂性1.QUBO 的介入:寻找最佳药材组合 二、难题:QUBO矩阵未知的问题1.为什么这么难? 三、稀疏建模(Sparse Modeling)1. 欠定系统中的稀疏解2. L1和L2的选择: 三、压缩感知算法(C…...

矩阵:Input-Output Interpretation of Matrices (中英双语)
矩阵的输入-输出解释:深入理解与应用 在线性代数中,矩阵与向量的乘积 ( y A x y Ax yAx ) 是一个极为重要的关系。通过这一公式,我们可以将矩阵 ( A A A ) 看作一个将输入向量 ( x x x ) 映射到输出向量 ( y y y ) 的线性变换。在这种…...

excel 使用vlook up找出两列中不同的内容
当使用 VLOOKUP 函数时,您可以将其用于比较两列的内容。假设您要比较 A 列和 B 列的内容,并将结果显示在 C 列,您可以在 C1 单元格中输入以下公式: 这个公式将在 B 列中的每个单元格中查找是否存在于 A 列中。如果在 A 列中找不到…...

YoloV8改进策略:Head改进|DynamicHead,利用注意力机制统一目标检测头部|即插即用
摘要 论文介绍 本文介绍了一种名为DynamicHead的模块,该模块旨在通过注意力机制统一目标检测头部,以提升目标检测的性能。论文详细阐述了DynamicHead的工作原理,并通过实验证明了其在COCO基准测试上的有效性和效率。 创新点 DynamicHead模块的创新之处在于它首次尝试在一…...

两地的日出日落时间差为啥不相等
悟空去延吉玩耍,在下午4点多的时候发来一张照片,说,天已经黑了!我赶紧地图上看了看,延吉居然和北京差了大约15度的经度差,那就是大约一小时的时差哦。次日我随便查了一下两地的日出日落时间,结果…...

Android Https和WebView
系统会提示说不安全,因为网站通过js就能调用你的android代码,如果你确认你的网站没用到JS的话就不要打开这个开关,如果用到了,就添加一个注解忽略它就行了。 后来就使用我们公司的网站了,发现也出不来,后来…...

2.5.1 文件管理基本概念
文章目录 文件文件系统文件分类 文件 文件:具有符号名,逻辑上有完整意义的一组相关信息的集合。 文件包含文件体、文件说明两部分。文件体存储文件的真实内容,文件说明存放操作系统管理文件所用的信息。 文件说明包含文件名、内部标识、类型、…...

在 PowerShell 中优雅地显示 Python 虚拟环境
在使用 Python 进行开发时,虚拟环境管理是一个非常重要的部分。无论是使用 venv 还是 conda,我们都希望能够清晰地看到当前所处的虚拟环境。本文将介绍如何在 PowerShell 中配置提示符,使其能够优雅地显示不同类型的 Python 虚拟环境。 问题…...

K8S Ingress 服务配置步骤说明
部署Pod服务 分别使用kubectl run和kubectl apply 部署nginx和tomcat服务 # 快速启动一个nginx服务 kubectl run my-nginx --imagenginx --port80# 使用yaml创建tomcat服务 kubectl apply -f my-tomcat.yamlmy-tomcat.yaml apiVersion: apps/v1 kind: Deployment metadata:n…...

观察者模式(sigslot in C++)
大家,我是东风,今天抽点时间整理一下我很久前关注的一个不错的库,可以支持我们在使用标准C的时候使用信号槽机制进行观察者模式设计,sigslot 官网: http://sigslot.sourceforge.net/ 本文较为详尽探讨了一种观察者模…...

python使用pip进行库的下载
前言 现如今有太多的python编译软件,其库的下载也是五花八门,但在作者看来,无论是哪种方法都是万变不离其宗,即pip下载。 pip是python的包管理工具,无论你是用的什么python软件,都可以用pip进行库的下载。 …...

C#(委托)
一、基本定义 在C#中,委托(Delegate)是一种引用类型,它用于封装一个方法(具有特定的参数列表和返回类型)。可以把委托想象成一个能存储方法的变量,这个变量能够像调用普通方法一样来调用它所存…...

《点点之歌》“意外”诞生记
世界是“点点”的,“点点”是世界的。 (笔记模板由python脚本于2024年12月23日 19:28:25创建,本篇笔记适合喜欢诗文的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《 …...