Linux操作系统之信号:保存与处理信号

目录

前言:

前文回顾与补充:

信号的保存

PCB里的信号保存 

sigset_t

信号集操作函数

信号的处理

信号捕捉的流程:​编辑

操作系统的运行原理

硬件中断

时钟中断

死循环

软中断 

总结:


前言:

在上一篇文章,我已经为大家详细的阐述了信号产生的物种方式,相信大家对于信号已经有了较为深刻的印象与理解了。

那么今天我们就来继续谈论信号的另外两个重要话题:信号的保存与捕捉。

前文回顾与补充:

我们之前说, 捕捉信号一共有三种方式,分别是:默认,忽略,与自定义。

在使用signal时,有两个宏分别代表默认与忽略的处理:

#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{::signal(2,SIG_IGN);//ignore 忽略:本身就是一种处理方法,什么也不用做直接忽略掉::signal(2,SIG_DFL);//default:执行该信号默认的处理方法return 0;
}

我们之前说的所有信号的产生,最后都要由OS执行,这是为什么呢?

:因为OS是进程的管理者

信号的处理是否会被立即处理?

:不会,会在合适的时候进行处理

一个信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适?

:需要,会被记录在进程的PCB中

⼀个进程在没有收到信号的时候,能否能知道,⾃⼰应该对合法信号作何处理呢?
:进程的处理方法一早就被设置好了
而进程时如何被存储到PCB中的呢?只是我们上节课说的位图结构吗?
这就要涉及我们今天的内容:信号的保存了!!

我们先补充一点信号的其他知识:

实际执行信号的处理动作被称为信号递达(Delivery)

信号从产生到递达之间的状态,被称为信号未决(Pending)

进程可以选择阻塞(block)某个信号

被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才会执行递达的动作。

这里要注意,阻塞与忽略是不同的,只要信号被阻塞就不会递达,而忽略时在递达之后我们选择的一种处理信号的动作


信号的保存

PCB里的信号保存 

我们刚刚说,由于信号的处理不是立即处理,所以我们需要保存好进程,就保存在进程的PCB中,我们先来看一下具体保存信号的结构:

这个pending就是我们之前说的信号位图,保存这个进程是否收到了相应的信号。

这个blcok也是一个位图,保存的是对应位置的信号是否被我们阻塞/屏蔽了。

而这个handler,则是一个函数指针数组,里面存储的就是对应信号的默认处理方法。我们信号的编号-1,就是对应的数组下标。

这也就是为什么我们只需要signal一次,就能永久改变处理方法的原因:因为我们是直接把方法拷贝替换成了自己的处理函数。

每个信号都有两个标志位分别表⽰阻塞(block)和未决(pending),还有⼀个函数指针表⽰处理动
作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上
图的例⼦中,SIGHUP信号未阻塞也未产⽣过,当它递达时执⾏默认处理动作。
SIGINT信号产⽣过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻
塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,⼀旦产生SIGQUIT信号将被阻塞,它的处理动作用户自定义函数sighandler。  

sigset_t

从上图来看,每个信号只有⼀个bit的未决标志, ⾮0即1, 不记录该信号产生了多少次,阻塞标志也是这样表示的。因此, 未决和阻塞标志可以用相同的数据类型sigset_t来存储,这个sigset_t称为信号集 , 这个类型可以表示每个信号的“有效”或“无效”状态,。
在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞, 而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的 信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

信号集操作函数

由于保管信号的是位图结构,我们不好每次都进行位操作来修改位图,所以有对应的系统调用:

函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含
任何有效信号。一般我们用这个是为了给自己定义的sigset_t的变量初始化。
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号
包括系统⽀持的所有信号。
注意,在使⽤sigset_tt类型的变量之前,⼀定要调用sigemptyset或sigfillset做初始化,使信号集处于
确定的状态。初始化sigset_t变量之后就可以在调⽤sigaddset和sigdelset在该信号集中添加或删
除某种有效信号。
以上四个函数都是成功返回0,出错返回-1。sigismember是⼀个布尔函数,⽤于判断⼀个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。
此外,还有一个系统调用,我们专门用来读取或更改进程的信号屏蔽字(阻塞信号集)。
如果oldset是⾮空指针,则读取进程的当前信号屏蔽字通过oldset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oldset和set都是⾮空指针,则先将原来的信号 屏蔽字备份到oldset⾥,然后 根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了 how参数的可选值。

 

如果我们想要恢复成老的数据,该怎么办?

所以有输出型参数oldset供我们使用。

如果调⽤sigprocmask解除了对当前若⼲个未决信号的阻塞,则在sigprocmask返回前,⾄少将其中⼀
个信号递达。
还有一个系统调用:sigpending
我们通过传递一个参数set,可以获取当前进程的未决信号集。
以上几个方法,就实现了我们对位图结构的简单的增删查改。
我们可以写一段代码来加深理解:

 

#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>int main()
{//初始化,把指定的位图全部清0sigset_t block,oblock;sigemptyset(&block);sigemptyset(&oblock);//设置信号,此时我们有没有把对2号信号的屏蔽,设置进入内核中?:只是在用户栈上设置了block的位图结构// 并没有设置进入内核中!sigaddset(&block,2);//把我们对2号信号的屏蔽,设置进入内核中sigprocmask(SIG_SETMASK,&block,&oblock);while(true){//获取该进程的pending表并打印sigset_t pending;sigpending(&pending);std::cout<<getpid()<<":";for(int i=31;i>=0;i--){if(sigismember(&pending,i))//挨个挨个检查是否存在在该位图里{std::cout<<"1";}else{std::cout<<"0";}}std::cout<<std::endl;sleep(1);}return 0;
}

运行代码,我们打开另外一个bash给该进程发送信号

那我们此时在增加一段代码,使得他在一定时间后自动解除屏蔽试试??

#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>void handler(int signo)
{std::cout<<"我已经解除屏蔽"<<signo<<std::endl;
}int main()
{signal(2,handler);//初始化,把指定的位图全部清0sigset_t block,oblock;sigemptyset(&block);sigemptyset(&oblock);//设置信号,此时我们有没有把对2号信号的屏蔽,设置进入内核中?:只是在用户栈上设置了block的位图结构// 并没有设置进入内核中!sigaddset(&block,2);//把我们对2号信号的屏蔽,设置进入内核中sigprocmask(SIG_SETMASK,&block,&oblock);int count=0;while(true){//获取该进程的pending表并打印sigset_t pending;sigpending(&pending);std::cout<<getpid()<<":";for(int i=31;i>0;i--){if(sigismember(&pending,i))//挨个挨个检查是否存在在该位图里{std::cout<<"1";}else{std::cout<<"0";}}std::cout<<std::endl;count++;if(count>=10){sigprocmask(SIG_SETMASK, &oblock, nullptr);//解除屏蔽}sleep(1);}return 0;
}

 运行代码,可以看见

 


信号的处理

 我们可以先说出结论,之前所说的合适的时候,指的就是,进程在从内核态切换为用户态时,会检测当前进程的pending与blcok,根据这两个位图是决定是否执行处理方法handler。

这里引出了两个新概念,用户态与内核态。

大家不用急,且听我慢慢道来:

信号捕捉的流程:

信号处理函数的代码是在⽤⼾空间的,处理过程还是比较复杂的,我们可以举例如下: 

用户程序注册了SIGQUIT 信号的处理函数 sighandler

当前正在执行main函数,这个时候会发生中断或者异常切换到内核态(为什么一定会发生中断我后面会解释)

在中断处理完毕后,要返回用户态的main函数前会检查到有信号SIGQUIT 递达。

内核决定返回用户态后不是恢复main函数的上下文继续执行代码,而是执行sighhandler函数(此时sighandler 和 main 函数使⽤不同的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是两个独⽴的控制流程

sighandler 函数返回后自动执⾏特殊的系统调⽤ sigreturn 再次进⼊内核态。
如果没有新的信号要递达,这次再返回⽤⼾态就是恢复 main 函数的上下⽂继续执⾏了。
所以总的过程可以总结为上图的一个无穷的形状,整个过程一共会进行四次内核态与用户态的切换。
所以我们可以先简单说明:执行自己的代码是用户态,执行系统调用是内核态。

操作系统的运行原理

我们这里不得不插一句嘴说一下操作系统的运行原理,只有这样才能让大家理解内核态与用户态更加深入。

这一内容还是很重要的,我们会填上之前说的很多坑。

硬件中断

我们之前在键盘产生信号时一直在说硬件中断,这个到底是怎么一回事呢?
 

当我们的硬件准备就绪时,就会开始中断,每一个硬件都有自己的一个中断号,并且进程会通过高电压的形式通知CPU,我已经准备就绪了。当我们的CPU知道硬件中断后,会去获取这个硬件对应的中断号。

此时,CPU会保护现场,包括存储此时运行进程代码数据的CPU的数据,保存在PCB中(之前讲页表时我们提到过保护现场这个现象) 。

在这之后,会根据中断号来进行处理的方法,即:


这个中断向量表IDT,本质是还是一个函数指针数组,每一个硬件对应的中断号,就对应了下标。

所以每一个硬件产生中断后,他的处理方法我们一开始就知道了,该如何处理。

这里的中断处理例程,一共有四步:
1、保存现场

2、根据中断号,查中断向量表

3、调用对应的处理方法

4、恢复现场

而这个中断向量表,是操作系统的⼀部分,启动就加载到内存中了。

通过外部硬件中断,操作系统就不需要对外设进⾏任何周期性的检测或者轮询

由外部设备触发的,中断系统运⾏流程,叫做硬件中断

 


时钟中断

但是我们还是有一个疑问,进程可以在操作系统的指挥下,被调度,被执⾏,那么操作系统⾃⼰被谁指挥,被谁推动执⾏呢?

外部设备可以触发硬件中断,但是这个是需要⽤⼾或者设备⾃⼰触发,有没有⾃⼰可以定期触发的 设备?

这里就要引出我们时钟中断的概念了

我们规定了一个时间,固定的触发硬件中断,这个中断,被我们称为时钟中断,负责定期帮我们实现中断!!

而这个中断在中断向量表的处理方法只有一个,那就是去调度进程!!

注意,调度进程不代表一定要进行进程切换。

还记得时间片这个概念吗?

其实就是一个整数count。

我们初始规定count=1000;

那么每一次进行调度,就回让count--,当count为0时,就会进行进程的切换。

所以我们还有主频这个概念,就是指 CPU 内部时钟信号的工作频率,通常以 赫兹(Hz) 为单位。你的CPU主频越高,价格越贵,同一个时间进行进程调度越多,性能越好。

这样,操作系统不就在硬件(时钟中断)的推动下,自动进行调度了么!!!
所以操作系统,就是基于中断向量表进行工作的。

死循环

如果是这样,操作系统不就可以躺平了吗?
对,操作系统⾃⼰不做任何事情,需要什么功能,就向中断向量表⾥⾯添加⽅法即可.操作系统的本质:就是⼀个死循环!
void main(void) /* 这⾥确实是void,并没错。 */
{         /* startup 程序(head.s)中就是这样假设的。 */
        ...
        /*
        * 注意!! 对于任何其它的任务,'pause()'将意味着我们必须等待收到⼀个信号才会返
        * 回就绪运⾏态,但任务0task0)是唯⼀的意外情况(参⻅'schedule()'),因为任
        * 务0 在任何空闲时间⾥都会被激活(当没有其它任务在运⾏时),
        * 因此对于任务0'pause()'仅意味着我们返回来查看是否有其它任务可以运⾏,如果没
        * 有的话我们就回到这⾥,⼀直循环执⾏'pause()'
        */
        for (;;)
        pause();
} // end main

软中断 

上述外部硬件中断,需要硬件设备触发。
有没有可能,因为软件原因,也触发上⾯的逻辑,我们不是说过软件中断吗?所以自然有!
为了让操作系统⽀持进⾏系统调用,CPU也设计了对应的汇编指令(int 或者 syscall),可以让CPU内
部触发中断逻辑。
用户层怎么把系统调⽤号给操作系统?
: 寄存器(比如EAX)
操作系统怎么把返回值给用户?
: 寄存器或者用户传入的缓冲区地址
系统调用的过程,其实就是先int 0x80、syscall陷⼊内核,本质就是触发软中断,CPU就会自动执
⾏系统调用的处理方法,而这个⽅法会根据系统调⽤号,自动查表,执⾏对应的⽅法
所以系统调⽤号的本质就是数组下标!
// sys.h
// 系统调⽤函数指针表。⽤于系统调⽤中断处理程序(int 0x80),作为跳转表。
extern int sys_setup (); // 系统启动初始化设置函数。 (kernel/blk_drv/hd.c,71)
extern int sys_exit (); // 程序退出。 (kernel/exit.c, 137)
extern int sys_fork (); // 创建进程。 (kernel/system_call.s, 208)
extern int sys_read (); // 读⽂件。 (fs/read_write.c, 55)
extern int sys_write (); // 写⽂件。 (fs/read_write.c, 83)
extern int sys_open (); // 打开⽂件。 (fs/open.c, 138)
extern int sys_close (); // 关闭⽂件。 (fs/open.c, 192)
extern int sys_waitpid (); // 等待进程终⽌。 (kernel/exit.c, 142)
extern int sys_creat (); // 创建⽂件。 (fs/open.c, 187)
extern int sys_link (); // 创建⼀个⽂件的硬连接。 (fs/namei.c, 721)
extern int sys_unlink (); // 删除⼀个⽂件名(或删除⽂件)。 (fs/namei.c, 663)
extern int sys_execve (); // 执⾏程序。 (kernel/system_call.s, 200)
extern int sys_chdir (); // 更改当前⽬录。 (fs/open.c, 75)extern int sys_time (); // 取当前时间。 (kernel/sys.c, 102)extern int sys_mknod (); // 建⽴块/字符特殊⽂件。 (fs/namei.c, 412)extern int sys_chmod (); // 修改⽂件属性。 (fs/open.c, 105)extern int sys_chown (); // 修改⽂件宿主和所属组。 (fs/open.c, 121)extern int sys_break (); // (-kernel/sys.c, 21)extern int sys_stat (); // 使⽤路径名取⽂件的状态信息。 (fs/stat.c, 36)extern int sys_lseek (); // 重新定位读/写⽂件偏移。 (fs/read_write.c, 25)extern int sys_getpid (); // 取进程id。 (kernel/sched.c, 348)extern int sys_mount (); // 安装⽂件系统。 (fs/super.c, 200)extern int sys_umount (); // 卸载⽂件系统。 (fs/super.c, 167)extern int sys_setuid (); // 设置进程⽤⼾id。 (kernel/sys.c, 143)extern int sys_getuid (); // 取进程⽤⼾id。 (kernel/sched.c, 358)extern int sys_stime (); // 设置系统时间⽇期。 (-kernel/sys.c, 148)extern int sys_ptrace (); // 程序调试。 (-kernel/sys.c, 26)extern int sys_alarm (); // 设置报警。 (kernel/sched.c, 338)extern int sys_fstat (); // 使⽤⽂件句柄取⽂件的状态信息。(fs/stat.c, 47)extern int sys_pause (); // 暂停进程运⾏。 (kernel/sched.c, 144)extern int sys_utime (); // 改变⽂件的访问和修改时间。 (fs/open.c, 24)extern int sys_stty (); // 修改终端⾏设置。 (-kernel/sys.c, 31)...   extern int sys_dup2 (); // 复制⽂件句柄。 (fs/fcntl.c, 36)extern int sys_getppid (); // 取⽗进程id。 (kernel/sched.c, 353)extern int sys_getpgrp (); // 取进程组id,等于getpgid(0)。(kernel/sys.c, 201)extern int sys_setsid (); // 在新会话中运⾏程序。 (kernel/sys.c, 206)extern int sys_sigaction (); // 改变信号处理过程。 (kernel/signal.c, 63)extern int sys_sgetmask (); // 取信号屏蔽码。 (kernel/signal.c, 15)extern int sys_ssetmask (); // 设置信号屏蔽码。 (kernel/signal.c, 20)extern int sys_setreuid (); // 设置真实与/或有效⽤⼾id。 (kernel/sys.c,118)extern int sys_setregid (); // 设置真实与/或有效组id。 (kernel/sys.c, 51)// 系统调⽤函数指针表。⽤于系统调⽤中断处理程序(int 0x80),作为跳转表。fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,sys_setreuid, sys_setregid};
所以缺⻚中断?内存碎⽚处理?除零野指针错误?这些问题,全部都会被转换成为CPU内部的软中断, 然后⾛中断处理例程,完成所有处理。有的是进⾏申请内存,填充⻚表,进⾏映射的。有的是⽤来处理内存碎⽚的,有的是⽤来给⽬标进⾏发送信号,杀掉进程等等。
所以,操作系统就是躺在中断处理例程上的代码块!
CPU内部的软中断,比如int 0x80或者syscall,我们叫做 陷阱,CPU内部的软中断,比如除零/野指针等,我们叫做 异常。(这也是“缺页异常”为什么这么叫的原因)

总结:

由于时间关系。

今天的博客就写到这里,明天我们将会进行最后的结尾,为大家更加具体的说一下什么是内核态与用户态。

相信今天的知识已经把大家之前所学的内容串联起来,大家对操作系统也有了更加深刻的理解!

明天我们将会完成信号部分的内容,并给大家讲一些信号done的相关内容,之后我们将会开始线程的学习!!

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

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

相关文章

Spring Boot 设置滚动日志logback

Spring Boot 的 logback 框架 Spring Boot 默认内置了 Logback 作为日志实现框架&#xff0c;只需要在resources文件夹下添加一个logback-spring.xml&#xff0c;springboot会按照你的设置自动开启logback日志功能。 配置 logback-spring.xml 实现每天产生一个日志文件&#xf…

如何定义一个只能在堆上或栈上生成对象的类

在C中&#xff0c;可以通过特定的技术手段来控制对象只能在堆(heap)或栈(stack)上创建。只能在堆上创建对象的类要实现这一点&#xff0c;我们需要阻止用户直接实例化对象&#xff0c;而只能通过new操作符创建。class HeapOnly { public:static HeapOnly* create() {return new…

1.1 前端-vue3项目的创建

构建工具先搭好vue3框架 vue2的vue-cli脚手架基于webpack构建工具创建vue的框架. 而在vue3&#xff0c;可以通过vite构建工具创建vue3项目&#xff0c;性能更优。 两者创建方式的区别&#xff1a;cmd命令基于的构建工具vue2/vue3vue create 项目名称&#xff08;或 vue ui图形化…

PHP password_get_info() 函数

password_get_info() 函数用于返回指定散列&#xff08;hash&#xff09;的相关信息。 PHP 版本要求: PHP 5 > 5.5.0, PHP 7 语法 array password_get_info ( string $hash ) 参数说明&#xff1a; $hash: 一个由 password_hash() 创建的散列值。 返回值 返回三个元素…

mac上的app如何自动分类

使用文件夹进行手动分类在Finder中创建文件夹&#xff0c;将同类应用拖入同一文件夹。右键点击Dock上的应用图标&#xff0c;选择「选项」→「在Finder中显示」&#xff0c;可快速定位应用安装位置。利用Launchpad自动分组打开Launchpad&#xff08;触控板四指捏合或按F4键&…

LLM面试题目 3

LLM面试题目 3 什么是自注意力机制(Self-Attention)?为什么它在LLM中很重要?如何评估LLM的性能?LLM面临的挑战有哪些?Transformer和RNN的区别是什么?LLM如何处理多轮对话? 题目讲解 什么是自注意力机制(Self-Attention)?为什么它在LLM中很重要? 自注意力机制是一种…

linux上的软挂载操作方法

针对linux上的软挂载 可以查看linux已经挂载和存储的磁盘分区 df -hfdisk 命令是检索相同信息的另一种方法&#xff0c;可以看到所有的磁盘分区 sudo fdisk -l 要将磁盘分区 /dev/sda1 挂载到 /home/visionx/EXD1 目录 步骤 1&#xff1a;准备工作 1.创建挂载目录&#xff08;如…

SecretFlow 隐语 (2) --- 隐语架构概览

在前边两篇文章中&#xff0c;介绍了数据要素和可信流通相关的内容&#xff0c;以及基于p2p模式的安装方法 SecretFlow 隐语 (1) --- 快速入门 关于在Linux上部署 SecretFlow --- P2P部署模式 由于安装过程中出现意外报错&#xff0c;现已提交issue等待官方技术人员查阅&#x…

PHP语言基础知识(超详细)第二节

二十七. 数组的遍历 1)通过函数进行遍历:(例:demo07) (此方式不能完全遍历数组,需要借助其他功能辅助)(不推荐,了解即可) key():返回数组中当前指针所在位置的键。 current():返回数组中当前指针所在位置的值。 例如:demo07: <?php/*key():返回数组中…

网络--OSPF实验

目录 OSPF实验报告 一、实验拓扑 二、实验要求 三、实验思路 1.IP地址划分 2. OSPF 部署 3. 其它配置 4. 验证测试 四、实验步骤 1.IP 地址配置 2.OSPF 部署 3.其它配置 4.验证测试 OSPF实验报告 一、实验拓扑 二、实验要求 1、R1-R3为区域0&#xff0c;R3-R4为…

Go语言第一个程序--hello world!

文章目录一、Go 语言程序安装二、运行程序三、go mod tidy 命令四、遇到的问题五、VS Code 调试 go 程序的相关配置说明一、Go 语言程序安装 Go语言下载链接&#xff1a;https://studygolang.com/dl 双击打开下一步下一步即可。 验证安装&#xff1a;go version 二、运行程序 创…

【MCU控制 初级手札】1.1 电阻

作者&#xff1a;电控工程手札 本博文内容著作权归作者所有&#xff0c;转载请务必保留本文链接 目录1. 定义2. 电导3. 电阻率4. 电导率5. 伏安特性6. 开路与短路7. 功率8. 应用元件特性&#xff08;端子特性&#xff09;&#xff1a;元件的两个端子的电路物理量之间的代数函数…

JS中async/await功能介绍和使用演示

JS 中 async/await 功能介绍与使用演示 一、功能介绍基本概念 async&#xff1a;用于声明异步函数&#xff0c;返回一个 Promise 对象。即使函数内没有显式返回 Promise&#xff0c;也会隐式将返回值封装为 Promise.resolve()。await&#xff1a;仅能在 async 函数内部使用&…

系统调用入口机制:多架构对比理解(以 ARM64 为主)

&#x1f4d6; 推荐阅读&#xff1a;《Yocto项目实战教程:高效定制嵌入式Linux系统》 &#x1f3a5; 更多学习视频请关注 B 站&#xff1a;嵌入式Jerry 系统调用入口机制&#xff1a;多架构对比理解&#xff08;以 ARM64 为主&#xff09; 本篇内容聚焦于系统调用的入口实现机…

java MultipartFile初始化

在Java中&#xff0c;MultipartFile 是Spring框架中用于处理文件上传的接口。​开发者通常不会直接初始化MultipartFile对象&#xff0c;而是通过Spring MVC的控制器方法参数接收上传的文件。如果需要在测试或模拟场景中创建其实例&#xff0c;可以使用Spring的MockMultipartFi…

Linux C IO多路复用

在上一节利用管道实现了一个简单的聊天室&#xff0c;但这个聊天室有一个很明显的问题就是&#xff0c;当A处于读阻塞情况下是不能向B发送消息的&#xff0c;只有收到B的消息才能发送。如何实现同时既能接受B的消息&#xff0c;又能向其发送消息&#xff1f;很遗憾&#xff0c;…

day21——特殊文件:XML、Properties、以及日志框架

文章目录一、特殊文件概述二、Properties属性文件2.1 文件特点2.2 Properties类解析2.3 写入属性文件三、XML文件详解3.1 XML核心特性3.2 XML解析&#xff08;Dom4J&#xff09;3.3 XML写入3.4 XML约束&#xff08;了解&#xff09;四、日志技术&#xff08;Logback&#xff09…

经典VB与现代VB(VB.NET)

Visual Basic&#xff08;VB&#xff09;目前其发展状态可以分为经典VB&#xff08;VB6及之前&#xff09;​和现代VB&#xff08;VB.NET&#xff09;​两个阶段。经典VB诞生于1991年&#xff0c;凭借​“快速开发&#xff08;Rapid Application Development, RAD&#xff09;”…

iOS UI视图面试相关

iOS UI视图面试相关 UITableVIew相关 重用机制 cell [tableView dequeueReusableCellWillIdentifier:identifer];其中A2、A3、A4、A5是完全显示在屏幕&#xff0c;A2、A6显示部分&#xff0c;A1和A7不在显示范围内&#xff0c;假如现在是从下滑时的结果&#xff0c;在A1消失时…

网络编程-tcp连接:服务器与客户端

使用服务器和客户端的代码&#xff0c;实现服务器和客户端的互相聊天功能 实现两台电脑之间互相聊天 方案一&#xff1a;服务器代码&#xff08;server.c&#xff09;#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h>…