yo!这里是Linux信号相关介绍

目录​​​​​​​

前言

基本介绍

概念

信号列表

信号处理

产生(发送)信号

通过按键产生

系统函数产生

软件条件产生

硬件异常产生

阻塞信号

信号状态

sigset_t

状态相关函数

1.sigprocmask

2.sigpending

捕捉信号

内核态与用户态

捕捉过程

sigaction

后记


前言

        先甭说linux信号,想一想生活当中存在哪些信号,有红绿灯、发令枪,手机提示音......,比如对于红绿灯而言,我们在过马路时候会看红绿灯并肉眼收到红绿灯信号,在看到红绿灯后我们也知道该做出什么动作,红灯停下绿灯直行。那么对于linux的信号,也是如此,os约定或规定了一些信号,在某时刻os发送给进程相应的信号,进程收到信号以后根据约定做出相应的动作,这就是关于信号的一个宏观的描述。

        在之前讲过地,当一个进程运行时,我们按下ctrl+c可以终止此进程,这里ctrl+c表示给进程发送2号信号,此信号地默认处理动作是终止,因此按下之后,进程就会立马终止掉,下面来看看关于信号产生和发送的具体细节吧。

基本介绍

  • 概念

        信号(signal)是一种进程间通信机制,是操作系统传递给进程的一种通知。它被用来通知进程发生了某种特殊情况,如外部事件的发生,如终端输入、中断、定时器到期等等。具体地,用户或os通过发送一定的信号通知进程,某些事件已经发生,你可以立即或者后续处理,因此信号产生与接收对于两个进程来说是异步的,进程必须将信号临时记下,方便后面处理。其中,异步表示两个进程互不等待,各自干各自的事,相反同步是一个进程发信号给另一个进程,之后这个进程等待另一进程的反馈后才继续运行,而异步则是不等待,发出信号后接着继续向后运行。

  • 信号列表

        如下图1,使用kill -l命令查看os定义的信号列表。其中1-31号信号属于普通信号,也是常见的一部分信号,32-64属于实时信号(不重点讨论)。其实看到这些信号都是大写的,应该知道是宏定义。当os规定这些信号时,也规定了默认的执行动作是什么,可通过man 7 signal命令查看,如下图2、3。

  • 信号处理

        对于上面的大部分函数,我们可以通过signal函数捕捉某信号来决定其处理方式,有三种方式可以选择:

        ①忽略,也就是不作为;

        ②默认,执行os规定的默认执行动作;

        ③自定义捕捉,用户提供一个处理函数,进程处理此信号时会执行此函数。

功能:修改(允许修改的)信号处理方式,

参数:signum传入信号编号,sighandler_t是函数指针,传入SIG_IGN表示捕捉动作为忽略,传入IGN_DFL表示捕捉动作为默认动作,传入你所提供的处理函数的函数名表示执行自定义行为,

返回值:若成功,返回值也是函数指针,是指向之前的信号处理函数的指针,若失败则返回SIG_ERR。

eg:

void func(int signnum)
{sleep(1);printf("\n%d号信号正在处理,pid:%d\n",signnum,getpid());
}int main()
{signal(2,func);while(1)sleep(1);return 0;
}

产生(发送)信号

        本质上,进程pcb内部具有保存信号的相关数据结构(位图),即信号位图字段(可以看到普通信号正好31个),通过修改0/1比特位来标识是否存在此信号,因此os向目标进程写信号——修改pcb中指定位图结构,完成信号发送,而此进程也是在合适的时候通过查看信号位图结构以进行相应动作。而信号的产生方式又分为以下几种,包括通过按键产生、系统函数产生、软件条件产生、硬件异常产生,下面分别介绍。

  • 通过按键产生

        按键产生信号,最常见的就是在前言中说过的ctrl+c发送2号信号SIGINT,默认处理动作是终止程序,此外还有3号信号SIGQUIT,按ctrl+\可以产生,默认处理动作是终止程序并且Core Dump

Core Dump解释:

        中文叫作核心转储,当一个进程异常终止时,用户可以选择允许产生core文件,把进程的用户内存数据结构全部保存到磁盘上,命名为【core.进程pid】。不止上面的3号信号可以core dump,很多信号都可以,具体可以通过man 7 signal查看信号手册。对于一个可以core dump的信号,默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,产生不安全的影响,但用户可以通过ulimit指令选择允许产生core文件,比如说允许core文件大小最大为1024k:ulimit -c 1024,而ulimit -c 0则是当用户不需要核心转储的时候不产生core文件,过程中可以通过ulimit -a来查看core文件大小等相关信息,如下图。

        如上就生成了core文件,使用gdb指令在调试过程中,输入【core-file core文件名】加载core文件即可来到产生信号的代码处进行调试,具体可看相关文章,这里不再赘述。

  • 系统函数产生

        之前讲过,我们可以通过指令【kill -信号编号 进程pid】给某进程发送信号,而kill指令是通过kill函数实现的,除此之外,还有raise函数、abort函数,这些都是os所提供的产生信号的系统函数,具体如下。

kill函数:给指定进程发送指定信号,

raise函数:给函数所在进程发送指定信号,

abort函数:给函数所在进程发送6号信号SIGABRT,直接终止。

        前两个函数都是成功返回0,失败返回-1,abort函数就像exit函数一样,总是成功执行,无返回值。

eg:

int main()
{int n=5;while(n--){sleep(1);printf("%d\n",n);}//kill(getpid(),2);raise(2);n=5;while(n--){sleep(1);printf("%d\n",n);}return 0;
}

  • 软件条件产生

        在之前的管道章节中说过,如果所有管道读端对应的文件描述符被关闭,则write操作会产生SIGPIPE信号,进而导致write进程退出,这里的SIGPIPE就是一种由软件条件产生的信号,此外如下的alarm函数可以产生SIGALRM信号,也是一种由软件条件产生的信号,alarm可以设定一个闹钟,在指定秒数后给当前进程发送SIGALRM信号,默认处理动作是终止当前进程。

        其中,seconds可以指定秒数,返回值是0或者是此闹钟之前所设定的闹钟还剩下的秒数。

eg:

void func(int signnum)
{sleep(1);printf("\n%d号信号正在处理,pid:%d\n",signnum,getpid());
}int main()
{signal(14,func);alarm(10);int n=10;while(n--){if(n==7){int ret2=alarm(3);cout <<"ret2:"<<ret2<<endl;}sleep(1);cout<<n<<endl;}return 0;
}

  • 硬件异常产生

        硬件异常被硬件以某方式检测到并通知内核,然后内核像该进程发送适当的信号。比如说,

①执行到除0的代码,cpu的运算单位会发生异常,os就会发送SIGFPE信号给此进程,本质上cpu内部的状态寄存器有对应的状态标记位(溢出标记位),os在计算完毕后检测到溢出标记位是1,则为异常,之后发送信号给进程;

②进程访问非法内存地址,比如空指针、越界等,MMU(Memory Manage Unit,硬件,与页表一起进行内存映射)会产生异常,os此时发送SIGEGV信号给进程,本质上访问非法地址就是在将虚拟地址转物理地址的过程中,MMU一定会报错,之后os会给对应进程发送信号。

        值得注意的是,发生硬件异常,进程不一定会退出,只是默认处理动作里有退出操作,当我们自定义行为,不进行exit等相关退出操作时,就会死循环,因为硬件异常一直未被处理。

eg:

void func(int signnum)
{sleep(1);printf("\n%d号信号正在处理,pid:%d\n",signnum,getpid());
}int main()
{signal(SIGFPE,func);int a=10;int b=0;int c=a/b;while(1)sleep(1);return 0;
}

阻塞信号

  • 信号状态

信号抵达(delivery):执行信号的处理动作

信号未决(pending):信号从产生到抵达的状态

阻塞(block):使信号保持在未决状态,直到解除才执行处理动作

        值得注意的是,阻塞和处理动作中的忽略不一样,信号被阻塞了就不会被抵达,而忽略是信号抵达了之后的一种处理动作。如下图是信号在内核中的相关数据结构示意图,在pcb中,存在block阻塞信号集、pending信号集、handlers处理方法表等相关数据结构。

block阻塞信号集:也叫信号屏蔽字,是一种位图结构,下标是信号编号,表中的1/0代表对应信号是否被阻塞

pending信号集:也是位图结构,下标是信号编号,表中的1/0代表对应信号是否是未决状态

handlers处理方法表:是一个函数指针数组,下标也是信号编号,当拿到信号编号signum时,并不是直接handlers[signum]()调用此函数,而是先判断(int)handers[signum]==0,则执行默认(SIG_DFL)动作,若==1(SIG_IGN)则执行忽略动作,若都不是然后才执行handlers[signum]()调用函数,其中SIG_DFL、SIG_IGN是由0、1强转成函数指针类型的宏定义,如下图:

        当os给某进程发送信号,也就是将此进程中的pending表中的对应位置由0置1,在合适的时候,进程会“遍历”pending表,遇到1后,不是直接调用handlers表中的函数,而是去block表中查看对应信号是否被阻塞,若block为1则不作为(等之后解除阻塞再说),若block为0则直接去调用对应函数。

  • sigset_t

        sigset_t是os提供的位图结构类型,与c++中的位图一样,但是我们不必究其实现细节,只需要记住关于它的操作函数,也就是由0置1、由1置0等相关接口,包括

#include <signal.h>
int sigemptyset(sigset_t *set);//将所有信号对应bit置0
int sigfillset(sigset_t *set);//将所有信号对应bit置1
int sigaddset (sigset_t *set, int signo);//将对应信号bit置1
int sigdelset(sigset_t *set, int signo);//将对应信号bit置0
int sigismember(const sigset_t *set, int signo);//判断对应信号bit是否为1

        上面前四个函数都是成功返回0,失败返回-1,后一个是bool函数,出错返回-1。

注意:使用sigset_t类型的变量之前一定要先使用sigemptyset、sigfillset接口初始化

 eg:

int main()
{sigset_t st;sigemptyset(&st);for(int i=31;i>=1;i--)//打印block表函数{if(sigismember(&st,i))cout<<"1";elsecout<<"0";}cout<<endl;sigaddset(&st,1);sigaddset(&st,2);sigaddset(&st,3);sigdelset(&st,3);//sigfillset(&st);for(int i=31;i>=1;i--)//打印block表函数{if(sigismember(&st,i))cout<<"1";elsecout<<"0";}cout<<endl;return 0;
}

  • 状态相关函数

1.sigprocmask

        sigprocmask函数可以修改或者读取当前进程的信号屏蔽字。如下,

set:一组将要添加或删除的信号集合

how:可以填SIG_BLOCK、SIG_UNBLOCK和SIG_SETMASK,SIG_BLOCK代表将set的信号增加到信号屏蔽字中,SIG_UNBLOCK代表将set中的信号从信号屏蔽字中删除,SIG_SETMASK表示直接将信号屏蔽字设置成set

oset:输出型参数,备份修改之前的信号屏蔽字(若不需要可置空),若未修改(即set为空),则该函数可得到信号屏蔽字

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

        举个例子,比如说你想要阻塞1、2、3信号,就要通过sigset_t接口将set对应1、2、3比特位由0置1,再传入函数,此时how就得设置成SIG_BLOCK,同时若你想要原来的信号屏蔽字,则在外面定义一个set变量放入oset,执行函数之后,则此set就是原来的信号屏蔽字。

eg:

void handler(int signum)
{cout<<"signum:"<<signum<<","<<"pid:"<<getpid()<<endl;
}int main()
{signal(2,handler);signal(3,handler);sigset_t set,oset;sigemptyset(&set);sigemptyset(&oset);sigaddset(&set,2);sigaddset(&set,3);sigprocmask(SIG_BLOCK,&set,nullptr);//屏蔽2、3号int n=10;while(n--){sleep(1);cout<<n<<endl;}sigdelset(&set,2);sigprocmask(SIG_UNBLOCK,&set,nullptr);//解除3号while(true)sleep(1);return 0;
}

2.sigpending

        sigpending函数可以读取当前进程的未决信号集,通过set这个输出型参数传出,成功返回0,失败返回-1。

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

 eg:

int main()
{sigset_t pendingset;sigemptyset(&pendingset);sigset_t blockset;sigemptyset(&blockset);sigaddset(&blockset,2);sigaddset(&blockset,3);sigprocmask(SIG_BLOCK,&blockset,nullptr);//屏蔽2、3号while (1) // 1秒打印一次pending表{sleep(1);sigpending(&pendingset);for (int i = 31; i >= 1; i--){if (sigismember(&pendingset, i))cout << "1";elsecout << "0";}cout << endl;}return 0;
}

         综上,我们可以去block一个信号,也可以去捕捉一个信号,那当我们把所有信号捕捉或者堵塞,那么是不是就写了个不会被异常或用户杀死掉的进程了?当然不是,os也想到这一点了,其中有一个信号9号信号(SIGKILL)不可被阻塞或者捕捉,可使用此信号终止任何进程。

捕捉信号

  • 内核态与用户态

        前面提到地,进程接受到信号,可能无法立即处理,需要在合适的时候处理,那什么时候合适呢?答:从内核态返回用户态的时候,进行信号检测和处理。

内核态:os执行自己代码的状态,此状态具有很高的优先级

用户态:执行用户写的代码时的状态,是一个受管控的状态

注意:

        ①内核态返回用户态之前因为什么进入内核态?因为需要进行系统调用、缺陷、陷阱及异常等

        ②cpu内有状态寄存器CR3来标识这两种状态

        我们知道,如下图,进程地址空间有4G,其中3G是用户地址空间,1G是内核地址空间,用户地址空间通过用户级页表映射到物理内存中,内核地址空间通过内核级页表(可以被所有进程看到)映射到物理内存上,顾名思义,用户写的代码“占用”的是用户地址空间,而内核地址空间“存储”的是os自带的系统调用代码等,内核地址空间在每个进程地址空间都有一份(可类比于动态库的调用模式)。因此在用户地址空间运行叫做用户态,在内核地址空间运行叫做内核态。

  • 捕捉过程

        如果信号的处理动作是用户自定义函数,在信号递达时调用这个函数,这称为捕捉信号如下图是信号捕捉的流程,可以看出,这像一个无穷大符号,以此来简化记忆。

注意:

  • 每个箭头穿过用户地址空间和内核地址空间的分界线的地方就是用户态和内核态的一次转化
  • 对于③,这一步就是上面说的先检查pending表,再看block表,然后去handlers表调用函数的过程,只不过这里是捕捉信号的过程,没有谈及处理动作是忽略或默认的情况,若处理动作是忽略,则将pending表对应比特位由1置0,再返回主函数中向下执行,若处理动作是忽略,则处理动作是默认,一般是终止,则停止调度此进程,将pcb、地址空间等释放,并且不用再返回主函数处
  • 对于④->⑤,想一下为什么执行完了自定义处理函数之后不直接返回到主函数的中断处,还要先回到内核地址空间再返回?因为一方面执行完handler函数后需回到内核将pending表对应位置由1置0,另一方面只有内核知道当初从主函数中断的地方,回到内核才能拿到关于此的上下文。

  • sigaction

        sigaction函数可以读取和修改与指定信号相关联的处理动作。

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); 

返回值:调用成功则返回0,出错则返回-1。

参数:

        signo:信号编号。

        act:若act指针非空,则根据act修改该信号的处理动作;

        oact:若oact指针非空,则通过oact传出该信号原来的处理动作。

其中struct sigaction是结构体,具体如下:

        其中,sa_handler变量传入自定义处理函数,sa_mask是sigset_t类型的变量,可以传入一个信号集,表示若想要调用此函数时自动屏蔽一些信号,可以把想要屏蔽的信号放进此变量中,其他的成员使用与实时信号有关,暂时不考虑,其中sa_flags可以设置为0。

eg:

void handler(int signum)
{cout << "signum:" << signum << ","<< "pid:" << getpid() << endl;
}int main()
{ struct sigaction act;act.sa_handler=handler;sigaction(2,&act,nullptr);while(true)sleep(1);return 0;
}

        捕捉信号不仅可以使用这里介绍sigaction函数,也可以使用前面介绍的signal函数,看以看出signal函数的使用比sigaction函数简单一些,但sigaction函数的功能比signal函数要多一些,下面看一下这两个函数的区别

①signal函数每次设置的信号处理函数只能生效一次,在执行完此处理函数后,随即将信号处理函数恢复为默认处理方式。所以如果想多次相同方式处理某信号,就得在处理函数中再次调用signal设置,如下。但是sigaction函数设置后并且执行了处理函数后,一直有效不会重置

②sigaction函数保证了在信号处理函数被调用时,系统建立的新信号屏蔽字会自动包括当前正在递达的信号。因此在处理一个给定信号时,如果这种信号再次发生,那么它会被阻塞到处理结束为止,同时sigaction结构体的sa_mask可以保证另外的你想要屏蔽的信号。

int handler(int signum)
{//...signal(SIGINT, handler);//...
}
int main()
{signal(SIGINT, handler);//...
}

后记

        信号这一章节的知识点不算特别难,但是算比较多的,还涉及到与线程部分耦合的知识点还未讲解,将会在后面的多线程章节进一步阐述,理解信号这一领域的知识点 不能与现实情境脱离,借助现实情况可简化理解难度,比如红绿灯发出的信号、信号枪发出的信号。在面试中,信号也是一个高频的考点,包括信号产生的过程,捕捉信号的过程,及涉及到其中的系统函数,都在本文章中一一介绍过,多看几遍,拜拜!


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

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

相关文章

Java面向对象思想以及原理以及内存图解

文章目录 什么是面向对象面向对象和面向过程区别创建一个对象用什么运算符?面向对象实现伪代码面向对象三大特征类和对象的关系。 基础案例代码实现实例化创建car对象时car引用的内存图对象调用方法过程 成员变量和局部变量作用范围在内存中的位置 关于对象的引用关系简介相关…

1.4 Postman的安装

hello大家好&#xff0c;本小节我们来安装一下Postman&#xff0c;好为我们后续的测试工作做准备。 首先&#xff0c;打开Postman的官网Postman API Platform 然后根据同学们自己电脑的操作系统来下载对应的Postman安装包。我这里拿windows来举例。我们点击windows的图标 会跳…

初识计算机网络

网络通信基础 1. IP地址2.端口号3.认识协议3.1协议分层 4. 网络数据传输的基本流程4.1 五元组4.2封装和分用 1. IP地址 IP地址主要用于表示网络主机,其他网络设备的网络地址,IP地址用于定位主机的网络地址 比如:发送快递的时候,需要知道对象的收货地址,才能将包裹送到目的地. …

Android:The emulator process for AVD Pixel_2_API_29 was killed

The emulator process for AVD Pixel_2_API_29 was killed 报错描述&#xff1a; 第一次安装Android studio好不容易解决gradle启动模拟器又出现了以下错误 The emulator process for AVD Pixel_2_API_29 was killed原因一&#xff1a; 需要安装Intel x86 Emulator Acceleer…

[GPT]Andrej Karpathy微软Build大会GPT演讲(下)--该如何使用GPT助手

该如何使用GPT助手--将GPT助手模型应用于问题 现在我要换个方向,让我们看看如何最好地将 GPT 助手模型应用于您的问题。 现在我想在一个具体示例的场景里展示。让我们在这里使用一个具体示例。 假设你正在写一篇文章或一篇博客文章,你打算在最后写这句话。 加州的人口是阿拉…

大数据技术之Hive(超级详细)

第1章 Hive入门 1.1 什么是Hive Hive&#xff1a;由Facebook开源用于解决海量结构化日志的数据统计。 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张表&#xff0c;并提供类SQL查询功能。 本质是&#xff1a;将HQL转化成MapReduce程序 …

基于ssm社区管理与服务的设计与实现论文

目录 摘 要 1 Abstract 2 第一章 绪论 3 1.1研究背景 3 1.2 研究现状 3 1.3 研究内容 4 第二章 系统关键技术 5 2.1 Java简介 5 2.2 MySql数据库 5 2.3 B/S结构 6 2.4 Tomcat服务器 6 第三章 系统分析 7 3.1可行性分析 7 3.1.1技术可行性 7 3.1.2经济可行性 7 3.1.3运行可行性…

死锁(面试常问)

1.什么是死锁 简单来说就是一个线程加锁后解锁不了 一个线程&#xff0c;一把锁&#xff0c;线程连续加锁两次。如果这个锁是不可重入锁&#xff0c;会死锁。两个线程&#xff0c;两把锁。 举几个例子&#xff0c;1.钥匙锁车里了&#xff0c;车钥匙锁家里了。2. 现在有一本书…

Dockerfile介绍

1. DockerFile介绍 dockerfile是用来构建docker镜像的文件&#xff01;命令参数脚本&#xff01; 构建步骤&#xff1a; 1、编写一个dockerfile文件 2、docker build 构建成为一个镜像 3、docker run运行镜像 4、docker push发布镜像&#xff08;DockerHub、阿里云镜像仓库…

CV计算机视觉每日开源代码Paper with code速览-2023.12.8

点击计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【显著目标检测】Texture-Semantic Collaboration Network for ORSI Salient Object Detection 论文地址&#xff1a;https://arxiv.org//pdf/…

CCF 202104-2:邻域均值--C++

#include<iostream> #include<bits/stdc.h>using namespace std;int A[601][601]; int n;//长宽都为n个像素double FindNeighborSum(int i,int j,int r,int A[][601]) {int sum0;//像素和 int gs0;//领域 中的像素个数 for(int xi-r;x<ir;x)//找到每一个领域像素…

uniapp实战 —— 自定义顶部导航栏

效果预览 下图中的红框区域 范例代码 src\pages.json 配置隐藏默认顶部导航栏 "navigationStyle": "custom", // 隐藏默认顶部导航src\pages\index\components\CustomNavbar.vue 封装自定义顶部导航栏的组件&#xff08;要点在于&#xff1a;获取屏幕边界…

C语言-WIN32API介绍

Windows API 从第一个32位的Windows开始就出现了&#xff0c;就叫做Win32API.它是一个纯C的函数库&#xff0c;就和C标准库一样&#xff0c;使你可以写Windows应用程序过去很多Windows程序是用这个方式做出来的 main()? main()成为C语言的入口函数其实和C语言本身无关&…

matlab信号分选系统算法-完整算法结构

matlab信号分选系统算法 针对得到的脉冲流PDW进行信号分选&#xff0c;包括重频恒定、重频抖动、重频参差和重频滑变四种脉间调制类型。   这里我们先进行数据的仿真&#xff0c;后续边仿真边分享思路&#xff1a;首先根据信号类型&#xff0c;分别产生重频恒定、重频抖动、重…

使用Nodejs搭建简单的web网页并实现公网访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 使用Nodejs搭建简单的web网页并实现公网访问 前些天发现了一个巨牛的人工智能学习网站&#xff…

docker-compose容器编排(单机一键拉起所有容器)

1、安装docker-compose实验 安装完成 2、yaml文件 &#xff08;1&#xff09;定义 一种直观的、以竖列形式展示序列化数据格式的标记语言&#xff0c;可读性高。类似于json格式&#xff0c;但语法简单 yaml通过缩进表示数据结构&#xff0c;连续的项目用-减号表示 &#x…

Excel: Python 如何干掉 VBA 系列 乙

以下内容为本人的学习笔记&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/k2XtfXS3GUt4r2QhizMOVg 创建工作表格 创建表格 xlwings 就可以协助创建插入了宏的 excel 表格。 先找到一个心满意足的目录&#xff0c;一般我…

Tcon基础知识

1、TCON&#xff0c;就是 Timing Controller 的缩写。从主芯片输出的要在 TFT 显示屏上显示的数据&#xff0c;在经过 TCON 模块后可以变换生成 Panel 可以直接利用的 DATA 信号和驱动器&#xff08;包括 source driver 和 gate driver&#xff09;的控制信号。 TV 市场上 TCO…

python-爬取壁纸

代理池的&#xff0c;防止IP 被封 找到图片真实地址 现在看到的只是图片的预览地址 (previews) 1.检查&#xff1a; 2.鼠标变为箭头时查看网页源代码 关于怎样在源代码中找到图片的真实地址 ??? 为什么在源代码界面 ctrl f 时候搜索的是 .png ??? 首先图片地址是以 .j…

恢复出厂设置后在 Android 上恢复照片的 6 种常用方法

恢复出厂设置可帮助您删除电子设备的所有信息并将其恢复到原始系统状态。但是&#xff0c;如果您不小心按下了恢复出厂设置按钮并从 Android 设备中删除了所有难忘的照片&#xff0c;该怎么办&#xff1f;好吧&#xff0c;您无需担心&#xff0c;因为可以通过以下一些方法来恢复…