Linux(九) 信号

目录

一、什么是信号

二、信号的种类

三、信号的产生

3.1 通过终端按键产生信号

Core Dump 核心转储

3.2 调用系统函数向进程发信号

3.3 由软件条件产生信号

3.4 硬件异常产生信号

四、信号的注册

五、信号的注销

六、信号的三种处理方式

七、信号的递达阻塞未决

八、信号集

sigprocmask

sigpending

九、信号的捕获

sigaction

十、可重入函数 

十一、volatile

十二、SIGCHLD


一、什么是信号

  1. 输入命令,在Shell下启动一个前台进程。
  2. 用户按下Ctrl+C,键盘输入产生一个硬件中断。
  3. 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行, CPU从用户态切换到内核态处理硬件中断。
  4. 终端驱动程序将Ctrl+C解释成一个SIGINT信号,操作系统OS将其记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
  5. 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。

Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步
(Asynchronous)的。

二、信号的种类

使用命令查看:kill -l

非可靠信号:1~31号信号,信号可能会丢失
可靠信号:34~64号信号,信号不可能丢失

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定义 #define SIGINT 2

编号34以上的是实时信号,本文只讨论编号34以下的信号,不讨论实时信号。这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal

SIGHUP:1号信号,Hangup detected on controlling terminal or death of controlling process(在控制终端上挂起信号,或让进程结束),ation:term

SIGINT:2号信号,Interrupt from keyboard(键盘输入中断,ctrl + c ),action:term

SIGQUIT:3号信号,Quit from keyboard(键盘输入退出,ctrl+ | ),action:core,产生core dump文件

SIGABRT:6号信号,Abort signal from abort(3)(非正常终止,double free),action:core

SIGKILL:9号信号,Kill signal(杀死进程信号),action:term,该信号不能被阻塞、忽略、自定义处理

SIGSEGV:11号信号,Invalid memory reference(无效的内存引用,解引用空指针、内存越界访问),action:core

SIGPIPE:13号信号,Broken pipe: write to pipe with no readers(管道中止: 写入无人读取的管道,会导致管道破裂),action:term

SIGCHLD:17号信号,Child stopped or terminated(子进程发送给父进程的信号,但该信号为忽略处理的)

SIGSTOP:19号信号,Stop process(停止进程),action:stop

SIGTSTP:20号信号,Stop typed at terminal(终端上发出的停止信号,ctrl + z),action:stop

三、信号的产生

3.1 通过终端按键产生信号

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,现在我们来验证一下。

Core Dump 核心转储

首先解释什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部 保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K: $ ulimit -c 1024

 验证进程等待中的core dump位:

    pid_t id = fork();if(id == 0){sleep(100);int a = 10;//a /= 0;}int status = 0;waitpid(id,&status,0);cout << "父进程pid:" << getpid() << " 子进程pid:" << id << " exit code:" <<\(status & 0x7F) << " core dump:" << ((status >> 7) & 1) << std::endl;

为什么生产环境中一般都关闭core dump? 

因为现实中服务器可能会挂掉重启,如果每次挂掉都写core.xxx文件就会导致磁盘挤满,OS会出问题

然后写一个死循环程序:

前台运行这个程序,然后在终端键入Ctrl-C( 貌似不行)或Ctrl-\(介个可以):
 ulimit命令改变了Shell进程的Resource Limit,test进程的PCB由Shell进程复制而来,所以也具 有和Shell进程相同的Resource Limit值,这样就可以产生Core Dump了。 使用core文件:

gdb调试,自动帮我们定位哪一行代码出问题了 。

3.2 调用系统函数向进程发信号

首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号。
 

    cout << "我是一个进程,pid:" << getpid() << endl;while(1);

 

29228是test进程的id。之所以要再次回车才显示 Segmentation fault ,是因为在29228进程终止掉 之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和用 户的输入交错在一起,所以等用户输入命令之后才显示。
指定发送某种信号的kill命令可以有多种写法,上面的命令还可以写成 kill -SIGSEGV 29228或 kill -11 29228, 11是信号SIGSEGV的编号。以往遇 到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。

kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)。

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1。

abort函数使当前进程接收到信号而异常终止。

#include <stdlib.h>
void abort(void);
就像exit函数一样,abort函数总是会成功的,所以没有返回值。

6号信号,SIGABRT,通常用来终止进程

abort() = raise(6) = kill(getpid(),6)

如何系统调用接口?
用户调用系统接口->执行OS对应的系统调用接口->OS提取参数,或者设置特定的数值->OS向目标进程写入信号->修改对应进程的信号标记位->进程后续会处理信号->执行对应的处理动作 

3.3 由软件条件产生信号

SIGPIPE是一种由软件条件产生的信号,管道读端关闭,OS会给写端发送SIGPIPE信号,自动终止写端进程,读端关闭是软件条件不满足。

alarm:unsigned int alarm(unsigned int seconds);,收到14号信号,告诉内核在seconds秒后给进程发送SIGALRM信号,该信号默认处理动作为终止当前进程。

3.4 硬件异常产生信号

如何理解除0呢?
进行计算的是CPU,是个硬件,CPU内部是有寄存器的,状态寄存器(位图),有对应的状态标记位,溢出标记位,OS会自动进行计算完毕之后的检则!如果溢出标记位是1(CPU设置的),OS里面识别到有溢出问题,立即只要找到当前谁在运行提取PID,OS完成信号发送的过程,进程会在合适的时候,进行处理,一旦出现硬件异常,进程一定会退出吗?不一定!一般默认是退出,但是我们即便不退出,我们也做不了什么。
为什么会死循环?寄存器中的异常一直没有被解决!

 如何理解野指针或者越界问题?
1.都必须通过地址,找到目标位置
⒉我们语言上面的地址,全部都是虚拟地址

3.将虚拟地址转成物理地址

4.页表+MMU (memory manage unit ,硬件),页表是一部分硬件一部分软件

5.野指针->越界 -> 非法地址 ->MMU转化的时候一定会报错

四、信号的注册

在pcb中有一个未决(pending)信号集合(未决(pending)的意思是信号产生了但还没有决定怎么做),信号的注册就是指在这个pending集合中标记对应信号数值的二进制位为1

上面的话有些难以理解,我们先来看看在linux内核源码里一个进程的信号是如何保存的

在linux内核源码sched.h中的task_struct结构体里有这样一段关于信号的内容:

/* signal handlers */struct signal_struct *signal;struct sighand_struct *sighand;
​sigset_t blocked, real_blocked;sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */struct sigpending pending;

上面最后一行的sigpending结构体定义在signal.h中:

struct sigpending {struct list_head list;sigset_t signal;
};

这里的signal就是用来做信号标记的,给一个进程发送一个信号说白了就是在signal里标记一下这个信号曾经来过

那么signal是如何进行标记的呢?还得继续了解一下sigset_t这个结构体

在bits/sigset.h中进行了以下定义:

/* A `sigset_t' has a bit for each signal.  */
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct{unsigned long int __val[_SIGSET_NWORDS];} __sigset_t;

注:这里的__sigset_t其实就是sigset_t,只是一个类型名的重定义

在这个结构体中只有一个数组成员,这个数组里存放着一些数作为位图,位图的每一个二进制位就代表了一种信号,0表示未曾收到这个信号,1表示已经收到这个信号

这里需要注意的是,真正存放信号的是数组中某个数的某个二进制位,数组的存在只是因为单独一个数的二进制位存不下这么多种类的信号

现在我们就可以理解,当使用上述方式对某一个进程发送一个信号时,操作系统就会将该进程对应的pending集合中表示相应信号的位图的二进制位由0改为1

但是非可靠信号和可靠信号的注册还有一点区别

为了理解这种区别我们还应该了解一下list_head链表和signal.h中的sigqueue结构体

list_head是linux内核提供的一个用来创建双向循环链表的结构,由于这个结构是没有数据域的所以较为复杂,在这里不做深究。

我们需要知道的是,内核通过一个以list为表头的链表将所有产生的信号都串在了一起,链表中的每个节点的结构是一个sigqueue:

/** Real Time signals may be queued.*/
struct sigqueue {struct list_head list;int flags;siginfo_t info;struct user_struct *user;
};

这个结构体保存信号所携带的信息

现在我们就可以对非可靠信号和可靠信号的区别有一定的了解了

  • 1~31非可靠信号的注册:
    当试图对一个进程发送一个非可靠信号时,若发现位图上对应的位为0,则置为1,并在list_head链表里加入一个sigqueue节点;若发现位图上对应的位已经为1,则直接返回。简单地说就是若信号还未注册,则注册一下,若已经注册,则什么都不做
  • 34~64可靠信号的注册:
    当试图对一个进程发送一个可靠信号时,若发现位图上对应的位为0,则置为1,并在list_head链表里加入一个sigqueue节点;若发现位图上对应的位已经为1,对该位不进行操作但依旧在链表里加入一个节点。也就是说,每次对进程发送一个可靠信号时,不管该进程之前是否收到过相同的信号,总是会在list_head链表里加入sigqueue节点

对于信号来说,位图只是用来标记有没有待处理信号的,而节点才是信号真正注册的信息

五、信号的注销

看上文中信号的生命周期会发现,在处理信号之前,会先销毁信号的信息

信号注销存在的目的就是为了抹除信号存在的痕迹,防止对同一个信号进行多次处理

删除要处理的信号sigqueue节点:

  • 若信号是非可靠信号,则直接将位图置0(非可靠信号在没有处理之前只会注册一次)
  • 若信号是可靠信号,则删除后需要判断是否还有相同节点,没有的话才会重置位图为0

六、信号的三种处理方式

1. 忽略此信号。
2. 执行该信号的默认处理动作。
3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。

七、信号的递达阻塞未决

信号其他相关常见概念

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

  1. 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  2. SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
  3. 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本文不讨论实时信号。
  1. 信号的阻塞,并不会干扰信号的注册。信号能注册,但不能被立即处理,
  2. 将block位图中对应的信号比特位置为1,表示阻塞该信号
  3. 进程收到该信号,还是一如既往的注册
  4. 当进程进入到内核空间,准备返回用户空间的时候,调用do_signal函数,就不会立即去处理该信号了
  5. 当该信号不被阻塞后,就可以进行处理了

八、信号集

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

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
  • 这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种 信号,若包含则返回1,不包含则返回0,出错返回-1。

sigprocmask

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

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

  • 如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
  • 如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。
  • 如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。

假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

 如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

sigpending

#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。 下面用刚学的几个函数做个实验。程序如下:

#include <iostream>
#include <signal.h>
#include <assert.h>
#include <unistd.h>
using namespace std;void showpening(sigset_t &pending)
{for (int sig = 1; sig <= 31; sig++){if (sigismember(&pending, sig)){cout << "1";}else{cout << "0";}}cout << endl;
}
void handler(int signum)
{cout << "捕捉信号:" << signum << endl;
}
void blocksig(int sig)
{sigset_t bset;sigemptyset(&bset);sigaddset(&bset,sig);int res = sigprocmask(SIG_BLOCK,&bset,nullptr);assert(res == 0);(void)res;
}int main()
{for(int sig = 1;sig <= 31;sig++){blocksig(sig);}sigset_t pending;while(true){sigpending(&pending);showpening(pending);sleep(1);}return 0;
}

程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了所有信号,那岂不是任何信号都无法杀死进程了,OS考虑了这个问题,9号信号SIGKILL和19号信号SIGSTOP不会被阻塞

九、信号的捕获

内核态处理默认和忽略信号是很容易的,直接就做了,这里的是捕捉信号,所以重新进入用户态,其实OS可以直接执行sighandler,但是OS为了自身安全考虑,不会执行,还是会返回到用户态来执行。内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。

sigaction

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
  • sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:
  • 将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函 数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。
     

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。 sa_flags字段包含一些选项,本章的代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数,本章不详细解释这两个字段.

十、可重入函数 

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的 时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换 到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的 两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后 向链表中插入两个节点而最后只有一个节点真正插入链表中了。
像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为 不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

如果一个函数符合以下条件之一则是不可重入的:
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构

我们用的90%的函数都是不可重入的。

十一、volatile

int flag = 0;
void handler(int sig)
{
printf("chage flag 0 to 1\n");
flag = 1;
}
int main()
{
signal(2, handler);
while(!flag);
printf("process quit normal\n");
return 0;
}

 运行此代码发现当我们给进程发送2号信号的时候,进程并没有被终止,这是为什么呢?

正常是edx读取flag,进行修改或者判断。 但是编译器优化的时侯,编译器发现在main函数里没有任何一个语句是改flag的,所以编译器认为每次检测flag的时候都要edx检测一下内存太慢了,所以编译器就在第一次检测的时候把flag放入edx,之后每次检测的时候就自己检测edx不再访问内存了,所以我们在编写代码的时候必须显性的告诉编译器哪些代码不能被优化,就需要使用volatile修饰。

此时需要使用的编译代码是

g++ -o $@ $^ -std=c++11 -03 #03代表优化等级

十二、SIGCHLD

 进程一章讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻 塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第一种方式,父进程阻塞了就不 能处理自己的工作了;采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一 下,程序实现复杂。其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。
事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调 用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略 通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。
 

int main()
{signal(SIGCHLD,SIG_IGN); // 虽然SIGCHLD默认忽略,但默认忽略不会回收子进程,要显式的使用才会自动回收if(fork() == 0){cout << "child:" << getpid() << endl;sleep(5);exit(0);}while(true){cout << "parent:" << getpid() << "执行我自己的任务" << endl;sleep(1);}return 0;
}
void handler(int signum)
{cout << "子进程退出:" << signum << " parent pid:" << getpid() << endl;wait();//10 子进程都退出// while(wait())// 10 5个子进程退出?// 如果五个子进程退出,由于pending只有一位,所以父进程不知道有几个退出,所以父进程还会等待
}// 证明子进程退出,会给父进程发信号
int main()
{signal(SIGCHLD,handler);if(fork() == 0){cout << "child pid:" << getpid() << endl;sleep(3);exit(0);}while(true) sleep(1);return 0;
}

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

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

相关文章

摸鱼大数据——大数据导论

大数据导论 1、概念 大数据时代: 万物皆数据 ​ 数据概念: 人类的行为及产生的事件的一种记录称之为数据 ​ 数据价值: 对数据的内容进行深入分析&#xff0c;可以更好的帮助了解事和物在现实世界的运行规律 2、大数据诞生 大数据的诞生: 跟随着互联网的发展的,当全球互联…

【面试干货】一个数组的倒序

【面试干货】一个数组的倒序 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、实现思想 创建一个新的数组&#xff0c;然后将原数组的元素按相反的顺序复制到新数组中。 2、代码实现 package csdn;public class…

ssl证书价格一年多少钱?如何申请?

由于行业新规&#xff0c;现在阿里云、腾讯云等几乎所有平台都不再提供一年期免费证书&#xff0c;如果需要一年期证书则需要支付一定的费用。SSL证书的价格根据类型不同几十到几百上千不等。 一年期SSL证书申请通道https://www.joyssl.com/?nid16 一年期SSL证书申请流程&am…

13、24年--信息系统治理——IT审计

1、IT审计基础 1.1 IT审计定义 无重要的考点,自己读课本了解即可。 1.2 IT审计目的 1)IT审计的目的是指通过开展IT审计工作,了解组织IT系统与IT活动的总体状况,对组织是否实现IT目标进行审查和评价,充分识别与评估相关IT风险,提出评价意见及改进建议,促进组织实现IT目…

【网络安全】【Frida实战案例】某图xx付费功能逆向分析(一)

文章目录 一、目标应用二、环境三、步骤1、查看布局id2、用到的Log日志类信息3、尝试hook VIP判断方法 四、总结五、相关源码 1、【网络安全】【Frida实践案例】某图xx付费功能逆向分析&#xff08;一&#xff09; 2、【网络安全】【Frida实践案例】某图xx付费功能逆向分析&…

【多表查询】---------------------三大范式

1.笛卡尔积 #查询的是笛卡尔积 select * from tb_emp,tb_dept; #消除无效的笛卡尔积 select * from tb_emp,tb_dept where tb_emp.dept_id tb_dept.id; 2.内连接 外连接 内连接 内连接&#xff1a;交集 左外连接、右外连接 #隐式内连接 select tb_emp.name ,tb_dept.name…

TCP服务器实现将客服端发送的信息广播发送(使用内核链表管理客户端信息)

目录 1.服务器端实现思路 2.服务器端代码 3.客户端代码 4.内核链表代码 5.运行格式 一、服务器端 二、客户端 6.效果 1.服务器端实现思路 Tcp广播服务初始化 等待客户端连接 广播发送 2.服务器端代码 #include "list.h" #include <signal.h> #def…

如何看固态硬盘是否支持trim功能?固态硬盘开启trim数据还能恢复吗

随着科技的飞速发展&#xff0c;固态硬盘&#xff08;SSD&#xff09;已成为电脑存储的主流选择。相较于传统的机械硬盘&#xff0c;固态硬盘以其高速读写和优秀的耐用性赢得了广泛好评。而在固态硬盘的众多功能中&#xff0c;TRIM功能尤为关键&#xff0c;它能有效提升固态硬盘…

上传文件,服务器报500错误

项目场景&#xff1a; 今天项目上出现一个耗时比较长的问题&#xff0c;但是问题很简单&#xff0c;一开始没注意&#xff0c;导致耗时很久&#xff0c;到底是咋回事儿呢&#xff0c;请看下文~~ 问题描述 用户使用APP上传图片&#xff0c;出现 附件上传失败:服务器响应失败 的…

The 13th Shandong ICPC Provincial Collegiate Programming Contest

The 13th Shandong ICPC Provincial Collegiate Programming Contest The 13th Shandong ICPC Provincial Collegiate Programming Contest A. Orders 题意&#xff1a;有n个订单&#xff0c; 每日可生产k个产品&#xff0c;每个订单给出交付日和交付数量&#xff0c;是否能…

究极完整版!!Centos6.9安装最适配的python和yum,附带教大家如何写Centos6.9的yum.repos.d配置文件。亲测可行!

前言&#xff01; 这里我真是要被Centos6.9给坑惨了&#xff0c;最刚开始学习linux的时候并没有在意那么的&#xff0c;没有考虑到选版本问题&#xff0c;直到23年下半年&#xff0c;官方不维护Centos6.9了&#xff0c;基本上当时配置的文件和安装的依赖都用不了了&#xff0c…

OpenAI发布会最新消息!ChatGPT新功能发布!

关于即将发布的内容&#xff0c;OpenAI 官方帖子提供的唯一细节是&#xff0c;此次发布将更新 ChatGPT 及其最新模型 GPT-4。 OpenAI 员工程博文&#xff08;Bowen Cheng&#xff09;跟了个帖&#xff0c;「比 gpt-5 更酷」&#xff0c;不过又迅速删帖。 OpenAI 的葫芦里到底卖…

电商秒杀系统设计

业务流程 系统架构 系统挑战 高并发:秒杀活动会在短时间内吸引大量用户,系统需要能够处理高峰时期的大量并发请求 库存同步:在秒杀中,面临的一个严重系统挑战是如何确保在数以万计的用户同时抢购有限的商品时,如何正确、实时地扣减库存,以防止超卖现象。 防止恶意抢购和…

儿童护眼台灯哪个牌子好,适合儿童使用的护眼台灯推荐

护眼台灯在近几年成为家长和经常与电子设备打交道的人士中备受瞩目的家用电器。对于有孩子的家庭而言&#xff0c;它几乎成为了必备品&#xff0c;许多消费者已经对其有了一定的了解并进行了购买。然而&#xff0c;仍有部分家长对护眼台灯的效果和重要性缺乏充分认识&#xff0…

Dreamweaver 2021 for Mac 激活版:网页设计工具

在追求卓越的网页设计道路上&#xff0c;Dreamweaver 2021 for Mac无疑是您的梦幻之选。这款专为Mac用户打造的网页设计工具&#xff0c;集强大的功能与出色的用户体验于一身。 Dreamweaver 2021支持多种网页标准和技术&#xff0c;让您能够轻松创建符合现代网页设计的作品。其…

iisnginx环境一次奇怪的跨域问题解决经过

跨域问题描述&#xff1a; iis网站跨域、nginx 网站跨域 都已配置&#xff0c;访问接口依然出现跨域问题。 错误提示&#xff1a; ccess to XMLHttpRequest at ‘https://xxx.com/gameapi/preserve/get/status’ from origin ‘https://cdn.xxx.com’ has been blocked by CO…

技术前沿 |【大模型LLaMA:技术原理、优势特点及应用前景探讨】

大模型LLaMA&#xff1a;技术原理、优势特点及应用前景探讨 一、引言二、大模型LLaMA的基本介绍三、大模型LLaMA的优势特点五、结论与展望 一、引言 随着人工智能技术的飞速发展&#xff0c;大模型已成为推动这一领域进步的重要力量。近年来&#xff0c;大模型LLaMA以其卓越的…

每日一题——力扣206. 反转链表(举一反三、思想解读)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三题目链接 目录 菜鸡写法​编辑 代码点评 代码分析 时间复杂度 空间复杂度 专业点评 另一种方法​编辑 代码点评 代码逻辑 时间复杂度 空间…

基于火山引擎云搜索的混合搜索实战

在搜索应用中&#xff0c;传统的 Keyword Search 一直是主要的搜索方法&#xff0c;它适合精确匹配查询的场景&#xff0c;能够提供低延迟和良好的结果可解释性&#xff0c;但是 Keyword Search 并没有考虑上下文信息&#xff0c;可能产生不相关的结果。最近几年&#xff0c;基…

第三十二天 | 46.全排列 47.全排列||

终于进入排列&#xff01;&#xff08;之前都是组合&#xff09; 排列和组合的区别&#xff1a;在数学上的区别都懂&#xff0c;主要是看在代码实现上有什么区别 题目&#xff1a;46.全排列 树型结构比较简单 用used标记某一元素是否使用过。在组合问题中&#xff0c;其实是…