当前位置: 首页 > news >正文

linux系统编程(五)

1、信号

信号是事件发生时对进程的通知机制,针对每个信号都定义了一个唯一的整数,这些整数定义在signal.h中。

常见信号如下:

  • SIGABRT:进程调用abort函数,系统向进程发送此信号,终止进程并产生核心转储文件。
  • SIGBUS:表示出现了某种内存访问错误;
  • SIGCHLD:父进程的某一子进程终止;
  • SIGINT:用户输入终端终端字符(ctrl+c)
  • SIGKILL:必杀信号,程序无法阻塞、忽略或者捕获
  • SIGPIPE:向管道、FIFO或者socket写入信息时,没有相应的阅读进程;
  • SIGQUIT:键盘输入退出字符(ctrl+\)
  • SIGSEGV:程序对内存的引用无效时会产生此信号。

signal系统调用可以用来改变信号处置:

#include <signal.h>
void (*signal(int sig, void (*handler)(int)))(int);

第一个参数表示需要修改的信号,第二个参数handler是修改后的处置函数,返回值是之前的信号处置函数。

我们可以使用kill来发送信号

#include <signal.h>
int kill(pid_t pid, int sig);

参数pid用于标识一个或者多个目标进程:

  • pid大于0,发送信号给指定进程;
  • pid等于0,发送信号给与调用进程同组的所有进程,包括调用进程自身;
  • pid小于-1,向组ID等于该pid绝对值的进程组内下属进程发送信号;
  • pid等于-1,调用进程有权将信号发往的每一个目标进程,出去init和调用进程自身。特权进程发起这一调用,会发送信号给所有进程,这也被称为广播信号;

如果无进程与指定pid匹配,kill调用失败,errno设置为ESRCH。

除了可以使用kill发送信号,我们还可以使用raise发送信号:

#include <signal.h>
int raise(int sig);

raise是对自身发送信号,相当于调用kill(getpid(), sig)。

以下是示例代码:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>extern const char *const sys_siglist[];int sigIntCnt = 0;static void sigHandler(int sig) {switch(sig) {case SIGINT:printf("current sigIntCnt:%d\n", sigIntCnt++);if(sigIntCnt == 3) {exit(1);}break;case SIGQUIT:printf("recevie SIGQUIT, exit!\n");exit(1);break;default:printf("receive msg:%s\n", strsignal(sig));break;}
}int main(int argc, char **argv) {signal(SIGINT, sigHandler);signal(SIGQUIT, sigHandler);int n = 0;while(1) {printf("n:%d\n", n++);sleep(1);}return 0;
}

多个信号可以使用一个称之为信号集的数据结构来标识,该数据类型为sigset_t。下面是一组操作信号集的函数:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

创建sigset_t变量后,必须要用上面两个函数来初始化信号集,不能使用memset来初始化。

信号集初始化完成后,可以向信号集中添加或删除单个信号:

#include <signal.h>
int sigaddset(sigset_t *set, int sig);
int sigdelset(sigset_t *set, int sig);

可以用sigismember判断信号集中是否包含某个信号:

#include <signal.h>
int sigismember(const sigset_t *set, int sig);

内核会为每一个进程维护一个掩码(一组信号),阻塞其针对该进程的传递。信号掩码属于线程属性,多线程中每个线程都能使用pthread_sigmask来检查或修改信号掩码。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

使用sigprocmask可以修改信号掩码,也可以获取现有掩码。根据参数how可以确定给掩码带来的变化。

  • SIG_BLOCK:将set信号集内的信号添加到信号掩码中,做并集
  • SIG_UNBLOCK:将set中的信号从当前掩码中移除
  • SIG_SETMASK:将set信号集赋给信号掩码,替换

除了可以用signal来改变信号处置外,还可以使用sigaction做信号处置。

#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);struct sigaction {void (*sa_handler)(int);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
}

sa_mask定义了一组信号,在调用sa_handler所定义的处理器程序时将阻塞该信号。比如程序运行信号处理函数时,该信号再次到来,信号将不会中断自己。

pause会暂停进程的执行,知道信号处理函数中断该调用。

#include <unistd.h>
int pause();

信号处理函数有一种常见设计:

  • 信号处理函数设置全局性表示变量并退出,主程序对该标志进行周期性检查,发现置位就采取相应动作。进行这种周期性检查时可以让信号处理函数向一个专用管道写入一个字节数据。

我们在设计信号处理函数时要确保处理函数本身是可重入的。信号处理函数可能会更新errno,所以一般情况下进入信号处理函数时记录errno,退出时恢复errno。

如果想要让主程序和信号处理函数共享全局变量,可以进行如下声明:

volatile sig_atomic_t flag;

以下是一个简单的示例:

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>static void sigHandler(int sig) {printf("enter sigHandler, sig:%d\n", sig);int cnt = 3;switch(sig) {case SIGINT:while(cnt--) {printf("process SIGINT, current sleep cnt:%d\n", cnt);sleep(1);}break;case SIGQUIT:printf("process SIGQUIT\n");exit(1);break;default:break;}
}int main(int argc, char **argv) {struct sigaction sigact;sigemptyset(&sigact.sa_mask);sigaddset(&sigact.sa_mask, SIGINT);sigaddset(&sigact.sa_mask, SIGQUIT);sigact.sa_handler = sigHandler;sigaction(SIGINT, &sigact, NULL);signal(SIGQUIT, sigHandler);while(1) {sleep(1);}return 0;
}

2、进程

我们可以使用系统调用fork创建一个新的进程,子进程创建时会拷贝父进程的文本段、数据段、堆、栈,但是后续可以各自修改栈、堆中的数据,不影响另一个进程。进程创建完成后,两个进程都会从fork返回处继续执行。

#include <unistd.h>
pid_t fork();

无法创建子进程,fork返回-1。在父进程中fork返回新创建的子进程ID,在子进程中fork返回0。

fork时子进程会得到父进程文件描述符的副本,包含偏移量文件状态标志等。如果子进程更新了文件偏移量,那么也会影响到父进程中的文件描述符。

进程有两种中止方式,一种是异常(abnormal)中止,还有一种是_exit系统调用正常(normal)中止。

#include <unistd.h>
void _exit(int status);

_exit的参数status定义了中止的状态,父进程可以调用wait获取该状态。一般来说状态为0表示进程功成身退,非0值表示进程异常退出。

一般来说程序会使用库函数exit:

#include <stdlib.h>
void exit(int status);

exit会调用退出处理函数、刷新stdio缓冲区、调用_exit。

上面提到了wait,系统调用wait用于等待调用进程的任一子进程中止:

#include <sys/wait.h>
pid_t wait(int *status);

status返回的是子进程的中止状态。

wait调用之后,如果之前已经有子进程中止,wait会立即返回。如果没有子进程中止,调用会一直阻塞直至某个子进程中止。wait返回值为中止子进程的ID,参数status为子进程的返回状态。

如果进程没有子进程,那么wait会返回-1,同时errno会被设置为ECHILD。

以下是一个简单的代码示例:

#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/wait.h>int main(int argc, char **argv) {int fd = open("tmp.txt", O_RDWR | O_CREAT, 0644);if(fd < 0) {printf("open file failed, err:%s\n", strerror(errno));exit(1);} else {printf("open file success, fd:%d\n", fd);}pid_t pid = fork();if(pid == 0) {printf("Hello, I am child process, id:%d, parent:%d\n", getpid(), getppid());} else if (pid > 0) {printf("Hello, I am parent process, id:%d, child:%d\n", getpid(), pid);} else {printf("fork fail\n");exit(1);}const char *str = pid==0?"child":"parent";off_t offset = lseek(fd, 0, SEEK_CUR);printf("%s pid:%d fd:%d, offset:%ld\n",str, pid, fd, offset);const char *txt = "today is 2024/12/21, now 21:24!";if(pid == 0) {ssize_t written = write(fd, txt, strlen(txt));if(written < strlen(txt)) {printf("%s pid:%d, write fail, written:%ld\n", str, getpid(), written);} else {printf("%s pid:%d, write OK, written:%ld\n", str, getpid(), written);}exit(0);}pid_t child = wait(NULL);if(child > 0) {printf("%s pid:%d, child exit:%d\n", str, getpid(), child);} else {printf("%s pid:%d, child exit fail:%s\n", str, getpid(), strerror(errno));}offset = lseek(fd, 0, SEEK_CUR);printf("%s pid:%d fd:%d, after child write offset:%ld\n",str, pid, fd, offset);close(fd);return 0;
}// 测试结果
/*
open file success, fd:4
Hello, I am parent process, id:8424, child:8425
parent pid:8425 fd:4, offset:0
Hello, I am child process, id:8425, parent:8424
child pid:0 fd:4, offset:0
child pid:8425, write OK, written:31
parent pid:8424, child exit:8425
parent pid:8425 fd:4, after child write offset:31
*/

wait有很多限制:

  • 父进程创建了多个子进程,wait无法等待某个特定子进程完成,只能按顺序等待下一个子进程的中止;
  • 如果子进程退出,wait总是保持阻塞;
  • wait只能发现已经中止的进程,如果子进程因为某个信号而停止,或者收到型号恢复,wait就无能为例了。
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

参数pid表示要等待的具体子进程:

  • pid等于0,表示等待与调用进程同一进程组的所有子进程;
  • pid小于-1,等待进程标识符与pid绝对值相等的所有子进程;
  • pid等于-1,等待任意子进程,和wait等效。

option是一个掩码,它有以下几个选项:

  • WUNTRACED:除了返回中止子进程的信息外,还会返回因信号而停止的子进程信息;
  • WCONTINUED:因收到SIGCONT信号恢复执行的已经之子进程的状态信息;
  • WNOHANG:如果指定的子进程状态未发生改变,立即返回,不会阻塞。这种情况waitpid返回0。如果没有与指定参数相匹配的子进程,waitpid报错,errno设置为ECHILD。

某一子进程的父进程终止后,它的父进程会变成1(init),这是判断父进程是否存在的方法。

子进程死亡后,内核会将它转换为僵尸进程,父进程需要调用wait来释放子进程资源。如果父进程没有执行wait就退出,init进程会接管子进程并自动调用wait,将僵尸进程从系统移除。

如果父进程创建了许多子进程,但是没有执行wait,那么内核的进程表将永久为该子进程保留一条记录。如果有大量僵尸进程,并且填满了进程表,将会阻碍新进程的创建。这种情况下,只有杀死父进程,才能清理这些僵尸进程。

无论一个子进程什么时候中止,系统都会向父进程发送SIGCHLD信号,默认处理是忽略。我们可以通过安装信号处理程序来捕获它们,用wait来收拾僵尸进程。不过在之前的学习中,用sigaction时我们会设置屏蔽,处理SIGCHLD时如果有新的SIGCHLD到来,此时父进程只会处理一次,这样就可能有漏网之鱼了。所以解决方案是在信号处理函数中添加while:

while(waitpid(-1, NULL, WNOHANG) > 0) {}

以下是一个简单的代码示例:

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>static int cnt = 0;
static int total = 0;
static int quit = 0;
static void sigHandler(int sig) {pid_t pid = -1;switch(sig) {case SIGCHLD:while((pid = waitpid(-1, NULL, WNOHANG)) > 0) {printf("recyle pid:%d, cnt:%d\n", pid, cnt++);}if(quit && cnt == total) {printf("recyle last child process, cnt:%d, total:%d, QUIT!\n", cnt, total);exit(0);}break;case SIGINT:case SIGQUIT:if(cnt == total && cnt > 0) {printf("already recyle all child process! cnt:%d, total:%d\n", cnt, total);exit(0);}quit = 1;printf("pid:%d wait for child process stop, cnt:%d, total:%d\n", getpid(), cnt, total);break;default:break;}
}static void childSigHandler(int sig) {printf("pid:%d receive signal:%d, ignore\n", getpid(), sig);
}int main(int argc, char **argv) {struct sigaction act;sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGCHLD);sigaddset(&act.sa_mask, SIGINT);sigaddset(&act.sa_mask, SIGQUIT);act.sa_handler = sigHandler;sigaction(SIGCHLD, &act, NULL);sigaction(SIGINT, &act, NULL);sigaction(SIGQUIT, &act, NULL);pid_t pid = -1;total = 5;for(int tmp = 0; tmp < total; tmp++) {pid = fork();if(pid == 0) {sigset_t sigset;sigemptyset(&sigset);sigaddset(&sigset, SIGINT);sigaddset(&sigset , SIGQUIT);sigprocmask(SIG_BLOCK, &sigset, NULL);sleep((tmp+1)*2);printf("child process:%d exit!\n", getpid());exit(0);} else {printf("create child %d process OK, pid:%d\n", tmp, pid);}}while(1) {sleep(1);}return 0;
}

3、程序的执行

系统调用execve可以将新程序加载到进程的内存空间,这个过程中将丢弃旧有的程序,进程的栈、数据以及堆会被新程序的相应部件所替换。

#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);

参数pathname包含准备载入当前进程空间的新程序的路径名,可以是绝对路径也可以是相对于当前工作目录的相对路径。参数argv执行了传递给新进程的命令行参数。最后一个参数envp制定了新程序的环境列表,它是一个数组,元素格式为name=value。

execve成功调用后将永不返回,无需检查它的返回值,因为永远为-1。

还有其他的一些系统调用,它们都基于execve:

#include <unistd.h>int execle(const char *pathname, const char *arg, ...);
int execlp(const char *filename, const char *arg, ...);
int execvp(const char *filename, char *const argv[]);
int execv(const char *pathname, char *const argv[]);
int execl(const char *pathname, const char *arg, ...);

execlp和execvp(p:path)允许只提供程序的文件名,系统会在环境变量指定的目录列表中寻找相应的执行文件。

execle、execlp、execl要求开发者在调用中一字符串列表的形式来执行参数,而不是以数组来描述argv列表,首个参数对应于argv[0],必须以NULL指针来中止参数列表,实际填入时需要将NULL转换为char*。

execvp和execv允许开发者用vector(数组)定义参数列表。

execve和execle允许开发者通过envp为新程序显式指定环境变量,envp是以NULL结尾的字符串数组,这些函数以e(environment)结尾。

#include <stdlib.h>
int system(const char *command);

程序可以通过调用system函数来执行任意的shell命令。比如:system(“ls | wc”)。system的优点是简单,代价是低效率。

以下是一个简单的exec使用示例:

#include <stdio.h>int main(int argc, char **argv) {printf("myprint: ");for(int i = 0; i < argc; i++) {printf("%s ", argv[i]);}printf("\n");return 0;
}#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>int main(int argc, char **argv) {pid_t pid = fork();if(pid == 0) {printf("child process:%d start execle\n", getpid());execle("./myprint", "myprint", "Hello", "World", (char *)NULL, NULL);} else {pid = wait(NULL);printf("parent:%d recyle child:%d OK\n", getpid(), pid);}return 0;
}/*
child process:2920 start execle
myprint: myprint Hello World 
parent:2919 recyle child:2920 OK
*/

相关文章:

linux系统编程(五)

1、信号 信号是事件发生时对进程的通知机制&#xff0c;针对每个信号都定义了一个唯一的整数&#xff0c;这些整数定义在signal.h中。 常见信号如下&#xff1a; SIGABRT&#xff1a;进程调用abort函数&#xff0c;系统向进程发送此信号&#xff0c;终止进程并产生核心转储文…...

Effective C++ 条款 16:成对使用 `new` 和 `delete` 时要采取相同形式

文章目录 条款 16&#xff1a;成对使用 new 和 delete 时要采取相同形式核心思想示例代码错误用法分析设计建议总结 条款 16&#xff1a;成对使用 new 和 delete 时要采取相同形式 核心思想 一致性要求 当使用 new 分配内存时&#xff0c;必须在相应的 delete 操作中保持一致&a…...

【HarmonyOS NEXT】鸿蒙原生应用“上述”

鸿蒙原生应用“上述”已上架华为应用市场&#xff0c;欢迎升级了鸿蒙NEXT系统的用户下载体验&#xff0c;用原生更流畅。 个人CSDN鸿蒙专栏欢迎订阅&#xff1a;https://blog.csdn.net/weixin_44640245/category_12536933.html?fromshareblogcolumn&sharetypeblogcolumn&a…...

【人工智能】使用Python构建推荐系统:从协同过滤到深度学习

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 推荐系统是现代互联网的重要组成部分,广泛应用于电商、社交媒体和流媒体平台中。本文详细介绍了如何使用Python构建推荐系统,从传统的协同…...

店铺营业状态设置

admineShopController RestController("admineShopController") RequestMapping("/admin/shop") Api(tags "店铺相关接口") Slf4j public class ShopController {//设置一个常量 因为经常使用public static final String KEY "SHOP-ST…...

batchnorm和layernorm的理解

batchnorm和layernorm原理和区别 batchnorm 原理 对于一个特征tensor x ∈ R b c f 1 f 2 … x \in \mathbb{R}^{b \times c \times f_1 \times f_2 \times \dots} x∈Rbcf1​f2​… 其中&#xff0c; c c c是通道&#xff0c; f f f是通道中各种特征&#xff0c;batchno…...

在git commit之前让其自动执行一次git pull命令

文章目录 背景原因编写脚本测试效果 背景原因 有时候可以看到项目的git 提交日志里好多 Merge branch ‘master’ of …记录。这些记录是怎么产生的呢&#xff1f; 是因为在本地操作 git add . 、 git commit -m "xxxxx"时&#xff0c;没有提前进行git pull操作&…...

【Rust自学】6.3. 控制流运算符-match

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 6.3.1. 什么是match match允许一个值与一系列模式进行匹配&#xff0c;并执行匹配的模式对应的代码。模式可以是字面值、变量名、通配符等…...

大模型应用技术系列(三): 深入理解大模型应用中的Cache:GPTCache

前言 无论在什么技术栈中,缓存都是比较重要的一部分。在大模型技术栈中,缓存存在于技术栈中的不同层次。本文将主要聚焦于技术栈中应用层和底层基座之间中间件层的缓存(个人定位),以开源项目GPTCache(LLM的语义缓存)为例,深入讲解这部分缓存的结构和关键实现。 完整技术…...

『大模型笔记』评估大型语言模型的指标:ELO评分,BLEU,困惑度和交叉熵介绍以及举例解释

评估大型语言模型的指标:ELO评分,BLEU,困惑度和交叉熵介绍以及举例解释 文章目录 一. ELO Rating大模型的elo得分如何理解1. Elo评分的基本原理2. 示例说明3. 大模型中的Elo得分总结3个模型之间如何比较计算,给出示例进行解释1. 基本原理扩展到三方2. 示例计算第一场: A A…...

深度解析:Maven 和 Gradle 的使用比较及常见仓库推荐

Maven 和 Gradle 是 Java 项目中最常用的构建工具。它们各有优势&#xff0c;适用于不同的场景。本文将对两者进行详细的对比&#xff0c;并推荐一些常用的 Maven 和 Gradle 仓库&#xff0c;帮助开发者高效管理依赖。 一、Maven 和 Gradle 的使用比较 1.1 基本介绍 Maven 基…...

SQLite本地数据库的简介和适用场景——集成SpringBoot的图文说明

前言&#xff1a;现在项目普遍使用的数据库都是MySQL&#xff0c;而有些项目实际上使用SQLite既足矣。在一些特定的项目中&#xff0c;要比MySQL更适用。 这一篇文章简单的介绍一下SQLite&#xff0c;对比MySQL的优缺点、以及适用的项目类型和集成SpringBoot。 1. SQLite 简介 …...

管理面板Ajenti的在Windows10下Ubuntu24.04/Ubuntu22.04里的安装

Ajenti是一款基于Web的开源系统管理控制面板&#xff0c;可用于通过Web浏览器&#xff0c;管理远程系统管理性任务&#xff0c;这一点与 Webmin模块 非常相似。 Ajenti是一款功能非常强大的轻型工具&#xff0c;它提供了快速的、反应灵敏的Web界面&#xff0c;可用于管理小型服…...

在Python如何用Type创建类

文章目录 一&#xff0c;如何创建类1&#xff1a;创建一个简单类2&#xff1a;添加属性和方法3&#xff1a;动态继承父类4&#xff1a;结合元类的使用总结 二.在什么情境下适合使用Type创建类1. **运行时动态生成类**2. **避免重复代码**3. **依赖元类或高级元编程**4. **动态扩…...

Android学习19 -- NDK4--共享内存(TODO)

在安卓的NDK&#xff08;Native Development Kit&#xff09;中&#xff0c;C共享内存通常用于不同进程间的通信&#xff0c;或者在同一进程中多线程之间共享数据。这种方法相较于其他形式的IPC&#xff08;进程间通信&#xff09;来说&#xff0c;具有更高的性能和低延迟。共享…...

《Cocos Creator游戏实战》非固定摇杆实现原理

为什么要使用非固定摇杆 许多同学在开发摇杆功能时&#xff0c;会将摇杆固定在屏幕左下某一位置&#xff0c;不会让其随着大拇指触摸点改变&#xff0c;而且玩家只有按在了摇杆上才能移动人物&#xff08;触摸监听事件在摇杆精灵上)。然而&#xff0c;不同玩家的大拇指长度不同…...

RabbitMQ工作模式(详解 工作模式:简单队列、工作队列、公平分发以及消息应答和消息持久化)

文章目录 十.RabbitMQ10.1 简单队列实现10.2 Work 模式&#xff08;工作队列&#xff09;10.3 公平分发10.4 RabbitMQ 消息应答与消息持久化消息应答概念配置 消息持久化概念配置 十.RabbitMQ 10.1 简单队列实现 简单队列通常指的是一个基本的消息队列&#xff0c;它可以用于…...

【VScode】第三方GPT编程工具-CodeMoss安装教程

一、CodeMoss是什么&#xff1f; CodeMoss是一款集编程、学习和办公于一体的高效工具。它兼容多种主流平台&#xff0c;包括VSCode、IDER、Chrome插件、Web和APP等&#xff0c;支持插件安装&#xff0c;尤其在VSCode和IDER上的表现尤为出色。无论你是编程新手还是资深开发者&a…...

在JavaScript中,let 和 const有什么不同

在JavaScript中&#xff0c;let 和 const 是用于声明变量的关键字&#xff0c;但它们有一些重要的区别 1.重新赋值&#xff1a; let 声明的变量可以重新赋值。const 声明的变量必须在声明时初始化&#xff0c;并且之后不能重新赋值 let a 10; a 20; // 有效&#xff0c;a 的…...

Mysq学习-Mysql查询(4)

5.子查询 子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL4.1开始引入.在SELECT子句中先计算子查询,子查询结果作为外层另一个查询的过滤条件,查询可以基于一个表或者多个表. 子查询中常用的操作符有ANY(SOME),ALL,IN,EXISTS.子查询可以添加到SELECT,UPD…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

ip子接口配置及删除

配置永久生效的子接口&#xff0c;2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...