【关于Linux中----信号】

news2024/10/9 20:26:06

文章目录

  • 一、信号入门
    • 1.1 信号概念
    • 1.2 用 kill-l命令查看信号列表
    • 1.3 信号处理常见方式预览
  • 二、产生信号
    • 2.1 通过终端按键产生信号
    • 2.2 由于程序中存在异常产生信号
    • 2.3 系统接口调用产生信号
    • 2.4 软件条件产生信号
  • 三、阻塞信号
    • 3.1 信号相关常见概念补充
    • 3.2 在内核中的表示
    • 3.3 sigget_t及其操作函数
  • 四、处理信号
    • 4.1 内核如何实现信号的捕捉
    • 4.2 sigaction


一、信号入门

1.1 信号概念

信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动。信号是软中断,通常信号是由一个错误产生的。但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程。一个信号的产生叫生成,接收到一个信号叫捕获。

信号是发送给具体的进程的,告诉进程要在合适的时候执行相应的动作。这要求进程具有识别信号的能力,并且知道收到一个信号时,应该做出什么反应,这些是在操作系统的源代码中就已经设置好了的。

但是,当进程收到某种信号时,该信号并不一定是被进程立刻处理的,因为进程此时可能在做更重要的事情。

当进程不能立刻处理接受到的信号时,该信号会被进程保存起来,以便该信号在合适的时候被进程处理。而这个信号是被进程保存在struct task_struct中的,信号的本质也是数据。也就是说,信号的发送本质上就是向进程的PCB中写入信号数据。

PCB是一个内核数据结构,用来定义进程对象。但是操作系统不相信任何人,那么信号数据是怎么写入PCB的呢?

答案是操作系统本身。所以无论信号如何发送,本质都是在底层通过操作系统发送的。

1.2 用 kill-l命令查看信号列表

[sny@VM-8-12-centos practice]$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2
编号34以上的是实时信号,本章只讨论编号34以下的信号,不讨论实时信号。这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明: man 7 signal

在这里插入图片描述

1.3 信号处理常见方式预览

一般而言,进程收到信号的处理方案有三种。

①默认动作:终止进程、暂停进程等。
②忽略动作:不对进程做任何改变。
③自定义动作:用自己定义的接口替换到某一个进程原本应该让进程做出的反应(后文详细说明)。

二、产生信号

2.1 通过终端按键产生信号

当我们在Linux中编写的程序中包含死循环时,我们输入任何操作指令都不能让进程退出,除非输入ctrl+c这类指令,如下:
在这里插入图片描述

执行结果如下(该程序对应的Makefile太简单,这里就不粘贴了):

[sny@VM-8-12-centos practice]$ make clean;make
rm -f mytest
gcc -o mytest test.c
[sny@VM-8-12-centos practice]$ ls
Makefile  mytest  test.c
[sny@VM-8-12-centos practice]$ ./mytest
hello world!
hello world!
hello world!
^C

这里使用Ctrl +C的本质就是从键盘向进程发送2号信号,接下来简单地证明一下这个结论。

先了解一个函数接口:
在这里插入图片描述
这里的typedef void (*sighandler_t)(int)是一个函数指针,指针指向的函数可以完成对指定的信号在进程中执行动作的替换。
下面的sighandler_t signal(int signum, sighandler_t handler);是一个回调函数,第一个参数是某一个信号的编号,第二个参数是函数指针。使用这个函数就可以完成对于自己设定的信号执行动作的替换(类似qsort)。

举个例子:
在这里插入图片描述
执行结果如下:

[sny@VM-8-12-centos practice]$ ./mytest
hello world!
hello world!
hello world!
^Cget the signal: signal number:2 pid:28845
hello world!
hello world!
hello world!
^\Quit

可以看到,我们在代码中将2号信号的处理动作换为打印一条语句,而当我们输入Ctrl C的时候,进程没有退出,而是打印了这条语句,所以上面的结论是正确的!
注意,键盘产生的信号只能终止前台的进程,后台进程可以使用kill -9命令终止!9号信号不可被捕捉。

举个例子:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
   
 void handler(int signo)
 {
   switch(signo)
   {
    case 2:                             
      printf("捕捉2号信号\n");          
	break;
    case 3:                             
      printf("捕捉3号信号\n");          
      break;                                                                                                                                         
    case 9:                             
      printf("捕捉9号信号\n");          
      break;                            
    default:                            
      break;                            
  }                                                                  
}                                       
                                        
int main()                              
{                                       
  for(int i=1;i<=31;i++)                
  {//可以捕捉所有信号                   
    signal(i,handler);                  
  }                                     
  while(1)                                                                                                                                  
  {                                                                                                                                             
    printf("hello world!\n");                                                                                                               
    sleep(2);                                                                                                                               
  }
  return 0;
}        

执行结果如下:

至于为什么,下文再解释。

2.2 由于程序中存在异常产生信号

下面来写一个有明显错误的代码:
在这里插入图片描述
毫无疑问,最后一定会因为这个错误,导致该进程崩溃。
崩溃提示如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f mytest
gcc -o mytest test.c
test.c: In function ‘main’:
test.c:34:6: warning: division by zero [-Wdiv-by-zero]
     a/=0;
      ^
[sny@VM-8-12-centos practice]$ ./mytest
Floating point exception

这是一个浮点数错误。
接下来,将代码还原为2.1中最后的样子,并在handler函数稍作修改:
在这里插入图片描述

查看一下最后该进程收到的是几号信号。

[sny@VM-8-12-centos practice]$ make clean;make
rm -f mytest
gcc -o mytest test.c
test.c: In function ‘main’:
test.c:36:6: warning: division by zero [-Wdiv-by-zero]
     a/=0;
      ^
[sny@VM-8-12-centos practice]$ ./mytest
捕捉8号信号

也就是说,这个浮点数错误导致进程收到了8号信号,进而导致进程退出。为什么?

答:因为程序中的任何计算都会有CPU的参与,如果程序中出现了问题,CPU都会将其记录在案。而软件上的错误最终会体现在硬件或者其他软件上。而操作系统时硬件的管理者,当硬件出现问题时,操作系统会找到出错的进程,并向进程发送信号,导致进程终止。

当一个进程崩溃时,我们最关心的是什么?

毫无疑问一定是崩溃的原因,这个原因根据上一个问题就可以知道。
除此之外,我们往往还想知道,到底是进程中的那一部分出错导致了崩溃。

在Linux中,当一个进程推出的时候,它的退出码和退出信号都会被设置;当一个进程异常的时候,进程的退出信号会被设置,表明当前进程推出的原因。如果有必要,操作系统会设置推出信息中的core dump标志位,并将进程在内存中的数据转储到磁盘中,方便我们后期调试。

可是好像在上面的例子中,并没有看到进程运行结束后,有提示程序中那一部分出错的现象。这是因为在云服务器上,关于core dump的文件是被关掉的,其他的机器情况可能不同。

[sny@VM-8-12-centos practice]$ ulimit -a
core file size          (blocks, -c) 0//core dump相关文件
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7908
max locked memory       (kbytes, -l) unlimited
max memory size         (kbytes, -m) unlimited
open files                      (-n) 100001
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7908
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

将其打开只需执行如下指令:
在这里插入图片描述

接下来再运行程序,最后显示地结果就会不一样,而且会在当前目录下生成一个该程序的可以支持调试的二进制文件,如下:

[sny@VM-8-12-centos practice]$ ./mytest
Floating point exception (core dumped)
[sny@VM-8-12-centos practice]$ ll
total 136
-rw------- 1 sny sny 249856 Jan 17 20:45 core.26796
-rw-rw-r-- 1 sny sny     63 Jan 17 17:02 Makefile
-rwxrwxr-x 1 sny sny   8408 Jan 17 20:45 mytest
-rw-rw-r-- 1 sny sny    607 Jan 17 20:45 test.c

但是现在仍然没有显示到底是那个地方出错了,为了知道出错的地方,要调试起来,将Makwfile文件内容稍作修改:
在这里插入图片描述

然后运行程序,并开始调试,用生成的二进制文件进行调试:

[sny@VM-8-12-centos practice]$ gdb mytest
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/sny/code/practice/mytest...done.
(gdb) core-file core.26796
[New LWP 26796]
Core was generated by `./mytest'.
Program terminated with signal 8, Arithmetic exception.
#0  0x0000000000400595 in main () at test.c:36
36	    a/=0;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64

但是并不是所有的程序都会生成core文件,具体取决于该进程的core dump是否为1,举个例子查看core dump:
在这里插入图片描述
结果如下:

[sny@VM-8-12-centos practice]$ ./mytest
子进程!
exit code:0 exit signal:8 core dump:1

2.3 系统接口调用产生信号

我们也可以直接调用系统接口kill来产生信号:
在这里插入图片描述
样例代码如下:
在这里插入图片描述

接下来做以下操作:
在这里插入图片描述

可以看到,刚开始右边有一个sleep进程,但是当我们用左边的程序给sleep进程发送了9号信号之后,sleep进程就不存在了。因为它接收到了9号信号,所以退出。
以上就是通过系统接口发送信号的全过程。

第二个接口为raise,它的作用是给进程本身发送信号:在这里插入图片描述

样例代码如下:

int main()
{
  while(1)
  {
    printf("This is a process!\n");
    sleep(2);
    raise(9);                                                                                                                                        
  }                                                                                                                                  
}                                                                                                                                    

结果如下:

[sny@VM-8-12-centos practice]$ ./mytest
This is a process!
Killed

第三个接口abort是给进程本身发送6号信号,这个读者可以用捕捉信号的代码自己验证一下,由于代码大量相同,这里就不粘贴了。

2.4 软件条件产生信号

这种信号一般是通过某种软件(操作系统),来触发信号的发送,系统层面设置定时器,或者某种操作而导致条件不就绪等这样的场景下,触发的信号发送。比如:进程间通过管道通信时,读端关闭,写端还在写时,就会触发信号发送,关闭写端。

这里用到的接口是alarm:
在这里插入图片描述
这个接口的参数是时间,表示在一段时间之后向进程发送一个信号(14号信号),返回值为剩余的时间(秒)。

举个例子:
在这里插入图片描述

结果如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f mytest
gcc -o mytest test.c -g
[sny@VM-8-12-centos practice]$ ./mytest
This is a process,ret:0
res:25
This is a process,ret:0
res:0

如果没有设置alarm的模拟动作,则会执行默认动作,终止进程。
看一个例子:
在这里插入图片描述

这段代码是要统计一秒内能对count递增到多少,结果如下:
在这里插入图片描述
虽然信号产生的方式很多,但最终一定都是通过操作系统向目标进程发送的信号。

如何理解操作系统向进程发送信号?

前面说过,在PCB中一定有对应的数据变量,来保存进程是否收到了对应的信号。
其实,这个数据变量是一个位图结构,也就是说用一个数字的每一个比特位是否为1表示该进程是否收到某一个信号。
所以,操作系统向进程发送信号本质就是,向PCB中的信号位图写入比特位1。


三、阻塞信号

3.1 信号相关常见概念补充

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

3.2 在内核中的表示

在这里插入图片描述

上图中的pending就是前面解释过的位图结构,用来表示是否接收到某信号,而handler本质上是一个函数指针数组,其中每一个指针都指向一个参数为一个整形值,无返回值的函数,就类似于我们前面实现的handler函数。
当函数参数为0或1时,进程就对该信号进行默认和忽略操作,如果是其他值,就把指向的函数地址填入handler,以便做出对应的反应。

操作系统下定义的SIG_DFL和SIG_IGN如下:

[sny@VM-8-12-centos practice]$ grep -ER 'SIG_DFL|SIG_IGN' /usr/include/
/usr/include/asm-generic/signal-defs.h:#define SIG_DFL	((__sighandler_t)0)	/* default signal handling */
/usr/include/asm-generic/signal-defs.h:#define SIG_IGN	((__sighandler_t)1)	/* ignore signal */

而block表本质上也是一个位图结构,比特位的位置代表信号的编号,比特位内容表示是否收到了该信号。

进程在处理某一个信号的时候,首先要确定block和pending是否为1,同时为1则表示该进程被阻塞,若block为0,pending为1表示该信号可以被递达,也就可以进行具体的操作。

所以,上面的那张表应该横着看。

3.3 sigget_t及其操作函数

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

虽然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参数的可选值。

在这里插入图片描述
举个例子:

#include <stdio.h>
#include <signal.h>
#include <unistd.h> 

int main()
{
  sigset_t iset,oset;
  sigemptyset(&iset);
  sigemptyset(&oset);
  sigaddset(&iset,2);//屏蔽2号信号                                                                                                                   
  sigprocmask(SIG_SETMASK,&iset,&oset);  
  while(1)                               
  {                                      
    printf("hello world!\n");            
    sleep(3);                            
  }                                      
  return 0;                              
}  

结果如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f mytest
gcc -o mytest test.c -g
[sny@VM-8-12-centos practice]$ ./mytest
hello world!
hello world!
^Chello world!//输入2号信号对应操作,继续打印信息,2号信号被阻塞
^\Quit

注意,这样的操作同样对9号信号不起作用!

sigpending:

在这里插入图片描述
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。

举个例子:
在这里插入图片描述

上面代码的意思是先屏蔽2号信号,这样2号信号就不会被递达。
当进程跑起来时,pending中将全为0,而当向进程发送2号信号之后,pending中第二个比特位将会为1。

结果如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f mytest
gcc -o mytest test.c -g
[sny@VM-8-12-centos practice]$ ./mytest
0000000000000000000000000000000
0000000000000000000000000000000
^C0100000000000000000000000000000
0100000000000000000000000000000
^\Quit

可见,结果和预计相同。

四、处理信号

4.1 内核如何实现信号的捕捉

前面说过,信号被收到之后,进程会在合适的时候进行相关的操作,那么什么时候才是合适的时候呢?
答案是当进程从内核态转变为用户态的时候,这个时候,进程就会先在PCB中的pending位图里检测收到的信号,并做出处理。

内核态:执行操作系统的代码和数据时所处的状态
用户态:执行用户的代码和数据时所处的状态

它们的区别在于,内核态的权限要远远大于用户态的权限。

下面来详细地解释一下内核态和用户态:
在我之前的文章关于Linux中----进程优先级、环境变量和进程地址空间中解释过:每一个i昵称都有自己的虚拟空间,这些虚拟地址会经过页表和MMU映射到具体的物理内存。而每一个进程都有自己的虚拟空间和页表,这也保证了不同的进程不会占用同一块资源。

但是,在虚拟地址空间中除了有3GB的用户空间之外,还有1GB的内核空间,如下:
在这里插入图片描述
虽然每一个进程都有4GB的虚拟地址空间,但是并不意味着它们都可以随时随地访问这些空间。

上面说的页表指的是用户级页表,每个进程都有。实际上,还有一个内核级页表。这个页表被所有进程共享,但是共享不代表可以访问

只有当进程处于内核态时,才能经过那1GB的内核空间和内核级页表的映射访问到物理内存中属于操作系统的代码和数据。这也就是所谓的“权限的不同”。

当进程从用户态转变为内核态时,操作系统处理完所有的异常之后准备返回用户态之前,就会检测当前进程中的PCB的pending位图。如果某一信号的处理动作时自定义的,则返回用户态执行信号处理函数,然后再通过特殊的系统调用进入内核态,最后再返回用户态中上次中断的地方继续向下执行。

如图:
在这里插入图片描述
为什么一定要切换为用户态才能执行信号捕捉方法呢?

理论上来说,操作系统是可以执行用户的代码的。但是操作系统不相信任何人,为了防止操作系统执行的用户代码里存在恶意危害操作系统的代码的情况出现,每次执行信号捕捉代码都需要切换为用户态。

4.2 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函数调用,而是被系统所调用。

举个例子:
在这里插入图片描述

结果如下:

[sny@VM-8-12-centos practice]$ make clean;make
rm -f mytest
gcc -o mytest test.c -g
[sny@VM-8-12-centos practice]$ ./mytest
hello world!
hello world!
^C捕捉到2号信号
hello world!
hello world!
^\Quit

可以看到,这个方法跟之前介绍过的方法没有什么不同。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

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

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

相关文章

编码技巧——JDK版本切换/JDK7和JDK6下的文件输入输入

最近有使用低版本JDK实现文件输入输出的需求&#xff0c;主要是妹子计算机专业考研复试上级算法题&#xff0c;输入输入依赖文件而非纯算法代码&#xff0c;并且IDE一般使用JDK8以下的SDK&#xff0c;导致一些JDK8的API不适用&#xff1b;较早版本的JDK API代码是操作一大堆的缓…

jenkins部署过程

Jenkins 安装 示例服务器为 阿里云 CentOS 服务器。安全组中增加 8080 端口 Jenkins 默认占用 Jenkins 安装大体分两种方式&#xff0c;一种使用 Docker 另一种则是直接安装&#xff0c;示例选择后者。不管使用哪种方式安装&#xff0c;最终使用层面都是一样的。 Linux安装过…

Linux下源码安装nginx

一 安装步骤 nginx在linux下的源码安装&#xff0c;步骤还是比较简单的&#xff0c;主要分为以下 1. 下载安装包&#xff0c;这里选择&#xff1a;nginx-1.18.0.tar.gz 下载地址&#xff1a;http://nginx.org/en/download.html 2. 安装前置环境&#xff1a; yum install -y…

苹果手机字体大小怎么设置?简单实用,轻松学会

使用苹果手机的时候&#xff0c;发现苹果手机的字体看起来不是很舒服&#xff0c;想要将字体调大一点&#xff0c;却不知道怎么办。苹果手机字体大小怎么设置&#xff1f;其实方法很简单&#xff0c;今天小编就来具体的讲一下调整苹果手机字体大小的方法。 苹果手机字体大小怎么…

智能工厂中的设备如何实现远程监控和故障报警

智能工厂是在数字化工厂内&#xff0c;利用物联网技术和云计算计算加强设备信息管理水平&#xff0c;提高生产过程可控性、减少生产线人工干预&#xff0c;保证安全稳定的生产节奏&#xff0c;助力构建高效、节能、绿色、舒适、安全的工厂。 物通博联推出的智能工厂设备物联网…

C语言及算法设计课程实验四:选择结构程序设计

C语言及算法设计课程实验四&#xff1a;选择结构程序设计一、实验目的二、实验内容2.1、根据x的分段函数求对于的y值2.2、求小于1000正数的平方根2.3、百分制等级输出2.4、四个整数的顺序输出三、实验步骤3.1、选择结构程序设计实验题目1&#xff1a;根据x的分段函数求对于的y值…

四、GStreamer基础

本章介绍GStreamer的基本概念。理解这些概念对于阅读本指南的其他任何内容都是很重要的&#xff0c;它们都假定理解了这些基本概念。 元素 元素是GStreamer中最重要的一类对象。你通常会创建一个链接在一起的元素链&#xff0c;并让数据在这个元素链中流动。元素有一个特定的…

运动耳机有必要买吗、口碑最好的运动耳机品牌排行

冬天绝对是个减肥的好季节&#xff0c;因为这个季节天气比较冷&#xff0c;我们在运动过程中消耗的热量也就会更多&#xff0c;因此选择一款不错的运动耳机来用坚持就显得尤为重要了。这款运动耳机要能稳定在耳朵上&#xff0c;还要具备防水功能&#xff0c;同时音质上也要有保…

gd32f103vbt6 串口OTA升级-问题记录

今天研究了一下gd32单片机串口OTA升级的事情。我感觉ota的唯一好处就是不用调试器就可以下载&#xff08;更新&#xff09;单片机应用程序。(但是需要232串口&#xff0c;OTA程序我是使用stlink下载的&#xff01;&#xff01;) 可能有些同学要问&#xff0c;32的单片机本身就…

【综合】数字IC设计需要考虑的时序参数;Race Hazard;同步系统时序要求;建立时间、保持时间;偏斜;抖动;毛刺、竞争冒险;亚稳态

【综合】数字IC设计需要考虑的时序参数&#xff1b;Race Hazard&#xff1b;同步系统时序要求&#xff1b;建立时间、保持时间&#xff1b;偏斜&#xff1b;抖动&#xff1b;毛刺、竞争冒险&#xff1b;亚稳态数字设计时需要考虑的时序参数传播延迟 propagation delay, 上升时间…

十一、树结构的实际应用—堆排序

1、基本介绍 堆排序是利用堆这种数据结构而设计的一种排序算法&#xff0c;堆排序是一种选择排序&#xff0c;它的最好最坏平均时间复杂度均为O(n\log n) 。也不是稳定排序。堆是具有以下性质的完全二叉树&#xff1a;每个节点的值都大于或等于其左右孩子节点的值&#xff0c;…

云音乐实现注册功能

1. 新建web项目 Shop_SM2. 复制Shop资源和相关代码&#xff0c;实现注册功能回顾当前配置下的加载顺序1.MyBatis的主配置文件mybatis-config.xml &#xff08;在根目录下&#xff09;和 工具类MyBatisUtil&#xff08;注意总配置文件的路径&#xff09;2.实体类&#xff08;Use…

docker篇---pycharm连接docker,使用docker环境

pycharm连接docker&#xff0c;使用docker环境一、生成镜像和容器1.1 创建容器&#xff0c;需要加端口映射1.2 进入容器1.3 设置root密码&#xff0c;后续登录会用到1.4 修改配置文件1.5 重启ssh服务1.5 本机连接ssh二、pycharm连接docker2.1 允许远程客户端连接2.2 pycharm配置…

线程互斥与同步--Linux

文章目录线程互斥的概念与意义互斥的原理--原子性关于售票模拟的互斥应用死锁问题线程同步的概念与意义条件变量实现同步生产者消费者模型--互斥与同步基于阻塞队列的生产者消费者模型基于环形队列的生产者消费者模型POSIX信号量线程池线程安全下的单例模式总结线程互斥的概念与…

OMS标准 第二卷 主要通讯

版本4.1.2/2016-12-16 发布 1 引言 1.1 概述 本部分描述了从设备&#xff08;仪表或执行器或断路器&#xff09;和&#xff08;固定的&#xff0c;通常由市电供电的&#xff09;主设备&#xff08;网关或其他通信单元&#xff09;之间有线和无线通信的最低开放式计量系统要…

1. Mybatis 入门

文章目录1. Mybatis 简介2. Mybatis 快速入门3. 使用 idea 写 SQL4. Mapper 代理开发5. MyBatis 核心配置文件1. Mybatis 简介 MyBatis 是一款优秀的持久层框架&#xff0c;用于简化 JDBC 开发。 官方文档&#xff1a;https://mybatis.org/mybatis-3/zh/index.html 持久层&am…

【自学Docker】Docker cp命令

Docker cp命令 大纲 docker cp命令教程 docker cp 命令用于在本地文件系统与 Dokcer容器 之间复制文件或者文件夹。该命令后面的 CONTAINER 可以是容器Id&#xff0c;或者是容器名。 docker cp命令语法 从容器复制到宿主机 haicoder(www.haicoder.net)# docker cp [OPTION…

第57篇-某日头条signature参数分析【2023-02-01】

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析三、signature参数四、完整代码一、前言 今天来看一下新闻网站,分析一下参数 二、网站分析 网…

WebDAV之葫芦儿·派盘+一叶日记

一叶日记 支持WebDAV方式连接葫芦儿派盘。 推荐一款操作方便、界面简洁,记录生活点滴与心情,具有诗情画意的日记软件。 一叶日记是一款记录日记的手机软件,在这款软件中它里面有着各种不同的工具,可以方便用户去随时随地的记录日记,同时里面还有着各种不同的主题背景&…

补充:论Unity_InputSystemPacakage如何使用

图1补充一下默认特殊值如何设定&#xff0c;点击ProjectingSettings——InputSystemPacakage——Create Settings Asset 即可设置默认特殊值&#xff0c;或者点击图1中的Open input settings也可以打开此界面。 创建后会在Project窗口出现一个配置文件&#xff0c;不需要时删除…