Linux-信号

news2024/11/18 13:42:28

文章目录

      • 信号
          • 准备知识:
      • 信号产生的方式
          • 实验验证:9号信号是不可被捕捉(自定义的)
      • 信号处理:
        • 信号产生前:
          • 信号产生的方式:键盘
            • 实验显示:段错误(野指针)
            • 实验验证:浮点数错误8号信号。
            • 为什么我会收到信号呢?
          • 信号产生方式: 进程异常产生信号。
            • 实验验证:信号中止进程core_dump标志位是被设置了的事实
          • 信号产生方式:系统调用接口产生kill();
            • 实验验证:用系统接口kill()自主实现kill命令
          • 软件条件也能产生信号
            • 实验验证:统计一下alarm一秒钟,能够对一个整形值累加到多少(验证有无IO的效率差距)
        • 如何理解OS给进程发送信号?
        • 信号产生中
          • linux 在进程当中如何识别该信号
          • sigset_t 数据类型
          • sigprocnask()设置屏蔽字修改block位图
            • 实验:设置2号信号的屏蔽字
          • sigpending()处理pending位图
            • 实验:获取pending位图
          • singal函数就是用来修改handler 表的
        • 信号发送后
          • 内核态和用户态
          • 状态切换理解(何时进行信号捕捉)
        • 信号捕捉函数sigaction();
            • 实验:使用sigaction进行信号捕捉
      • 可重入函数:
      • volatile
      • SIGCHLD信号
            • 实验证明

信号

信号VS信号量,毫无关系

准备知识:

生活中信号:闹钟,红绿灯,烽火台。
当我们听到这些时,立马知道发生了什么。

  • 是不是只有这些场景发生才知道该怎么做吗?

不是,其实和场景是否被触发是没有直接关联的。信号的处理动作甚至远远早于信号的产生

  • 如何做到早就知道的呢?我们对特定事件的反应是被教育的,就是你记住的。

进程在没有收到信号的时候,知道如何识别并处理那一个信号。

  • 那进程是如何做到的呢?

    工程师代码设置好的。进程识别处理信号的这个能力是远远早于信号的产生的。

  • 信号是给进程发的,进程要在合适的时候执行对应的动作。
    收到某种信号并不是立即处理的。原因是信号随时都可能产生(异步),但是可能要做更总要的事情。进程收到某种信号的时候,并不是立即处理的而是在合适的时候。

  • 那么已经到来的信号是不是应该被暂时保存起来?是。
    进程收到信号之后,需要先将信号保存起来,以供在合适的时候被处理。

  • 应该保存在哪里呢?进程控制块struct task_struct{}中。
    信号本质也是数据。信号的发送就是往进程task_struct{}中发送数据。
    task_struct是一个内核数据结构,定义进程对象。内核不相信任何人,只相信自己。

  • 是谁向task_struct{}中写入数据呢?OS。无论信号如何发送,底层都是OS放的。

信号产生的方式

Ctrl C:就是向进程发送一个2号信号。

验证:通过signal注册对于2号信号的处理动作,改成我们自定义动作。注册handler的时候不是调用这个函数,只有当信号到来的时候才会调用。
在这里插入图片描述

kill -l :查看信号

在这里插入图片描述

signal();修改进程对信号的默认处理工作。

在这里插入图片描述

  1. 信号的产生方式其中一种就是键盘方式。

    ./mytest &是将进程转换到后台进程,键盘产生的信号,只能中止前台进程。
    kill -9 pid杀掉后台进程。
    kill -signal_number +pid:向进程发送信号。

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

    • 默认动作,一部分是中止自己,

    • 忽略动作,是一种信号的处理,只不过动作就是什么也不干。

    • (信号的捕捉)自定义动作,用singal()方法就是在修改信号的处理动作,默认-》自定义。

实验验证:9号信号是不可被捕捉(自定义的)

在这里插入图片描述

#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
void handler(int signo)
{
    switch(signo)
    {
        case 2:
            printf("hello xiaoawei,get a signal :%d\n",signo);
            break;
        case 3:
            printf("hello xiaoawei,get a signal :%d\n",signo);
            break;
        case 9:
            printf("hello xiaoawei,get a signal :%d\n",signo);
            break;
        default:
            break;    
    }
    // exit(1);
}
int main()
{
    int sig=1;
    for(sig;sig<=31;sig++)
    {   
        signal(sig, handler);
    }
    while(1)
    {
        printf("hello xiaoawei,pid:%d\n",getpid());
        sleep(2);
    }
    return 0;
}

信号处理:

信号产生前:

  1. 信号产生的方式:键盘
实验显示:段错误(野指针)

野指针或者内存错误就是段错误,就是进程的崩溃。为什么会崩溃?因为收到信号。
进程收到11号信号,造成进程崩溃。

在这里插入图片描述

int *p=NULL;
*p=100;
实验验证:浮点数错误8号信号。

int a =10;a/=0;

在这里插入图片描述

结论:在Windows或者Linux系统下,进程崩溃的本质及时进程收到对应的信号,然后进程执行信号对应的处理动作

为什么我会收到信号呢?

段错误就显示到虚拟地址对应时出现错误,在硬件上有所显示。CPU进程运算除零错误状态寄存器有所显示。软件上面的错误,通常会体现在硬件或者其他软件上。而OS是硬件的管理者,就要对于硬件的健康负责。你的进程导致出错,OS就会溯源到你的进程,并且向你的进程发送信号进程处理终止进程。try-catch就是语言层面上的信号捕捉。

  1. 信号产生方式: 进程异常产生信号。

程序中存在异常问题导致我们收到信号退出。

  • 当进程崩溃的时候,你最想知道什么?想知道原因

waitpid();status低7位可以获得退出信号,崩溃时可以得到原因。

  • 你还想知道啥?怎么解决,在哪一行崩溃的?为了方便后续调试

    core dump标志。进程如果异常的时候,被core dump,该位置会被设置为1.
    当一个进程退出的时候,他的退出码和退出信号都会被设置(正常情况)
    当一个进程异常的时候,进程的退出信号会被设置,表示当前进程退出的原因。
    如果必要,OS会设置退出信息中的core dump标志位,并将在内存中的数据转存到磁盘中,方便我们后期调试。

    core dump 默认是被关掉的,也就是0.当出现浮点数错误时并不会产生core dump文件。设置打开之后,会形成。

    在这里插入图片描述

利用GDB事后调试:makefile 后添加-g选项叫做可调试。
在这里插入图片描述

不一定所有的情况都会形成coredump文件。比如kill -9。

实验验证:信号中止进程core_dump标志位是被设置了的事实

在这里插入图片描述

  1. 信号产生方式:系统调用接口产生kill();

在这里插入图片描述

实验验证:用系统接口kill()自主实现kill命令

系统调用接口函数kill()向进程发送信号(命令行参数的使用)
在这里插入图片描述

raise 自己给自己发送信号。自举函数
abort()给自己发送sigabort6号信号。

在这里插入图片描述

  1. 软件条件也能产生信号

通过某种软件,来出发信号的传送,系统层面设置定时器
或者某种操作而条件导致不就绪等这样的场景下,触发信号发送。
alarm();14号信号
在这里插入图片描述

例子:进程间通信的时候,不读而且关闭读取入口的时候,一直在写没有意义的时候,OS向写端发送信号13号信号关闭了写端。
实验操作:5秒的进程,告诉OS,3秒之后给我发一个信号。返回值是剩余的2秒的时间。
在这里插入图片描述

跑了1秒钟直接取消:
在这里插入图片描述

实验验证:统计一下alarm一秒钟,能够对一个整形值累加到多少(验证有无IO的效率差距)
  1. alarm执行默认操作,不做信号捕捉统计

    在这里插入图片描述

    int main()
    {
        alarm(1);
        while(1)
        {
           printf("count:%d\n",count++);//不断地进行IO操作
        }
    
  2. 执行信号捕捉操作,1秒钟后中止。

    在这里插入图片描述

    int count=0;
    void handlerAlarm(int signo)
    {
        printf("count:%d\n",count);
        exit(1);
    }
    int main()
    {
        alarm(1);
        signal(SIGALRM,handlerAlarm);//信号捕捉是纯CPU和内存之间的操作
        while(1)
        {
            count++;
        }
    
  • 为何第一个很慢呢?
    纯CPU和服务器是非常快的。有IO信号传输打印到显示器还是很慢的。

结论:信号产生的方式种类虽然很多,但是无论产生信号的方式怎样,
但是一定是通过OS向目标进程发送的信号。

如何理解OS给进程发送信号?

信号的编号是有规律的[1,31]

struct task_struct{
	// 进程各种属性
	// 进程内部一定要有对应的数据变量来保存记录是否收到了对应的信号
	//用什么数据变量表示是否接收到信号呢?
	uint32_t sigs;//位图结构
    //0000 0000 0000 0000 0000 0000 0000 0000一共32位
	//所谓比特位的位置代表的就是哪一个信号,比特位的内容(0,1)代表的就是是否收到了信号
	//进程中采用位图表示是否收到信号。
};

本质是OS向指定进程的task_struct{}的信号位图写入比特位,即完成了信号的发送,信号的写入。

信号产生中

  • 实际执行信号的处理动作就成为信号传达。自定义 默认 忽略

  • 信号从产生到传达之间的状态成为信号未决。本质存在于task_struct的位图中

  • 进程可以选择阻塞某个信号。
    本质是OS允许进程暂时屏蔽信号。该信号仍然是未决的。
    信号不会被递达,直到解除阻塞,才能递达。

    • 忽略VS阻塞
      忽略是递达的一种方式,阻塞是没有被递达,递达。
linux 在进程当中如何识别该信号

进程控制块task_struct{}中有三张表block pending handler(函数指针数组)

  • pending确认位图是否收到了信号,保存的是已经收到但是还没有递达的信号
    OS发送信号的本质就是修改目标进程的pending 位图。

  • block:状态位图表示哪些信号不应该被递达,直到解除阻塞,才能递达。
    block表:本质上也是位图结构。uint32_t block;

    比特位的位置代表信号的编号
    比特位的内容,代表信号是否被屏蔽,阻塞如果此时某某一个信号位的pending收到了信号,如果是被阻塞的,就不看是否收到该信号
    如果是没有被block,如果再进行,该信号没有被block已经收到了,就证明可以抵达了
    也就是说block说的算,block表又叫做阻塞表,阻塞位图,也叫作信号屏蔽字

  • 函数指针数组 handler,处理信号执行自定义的函数方法
    里面的函数都是程序员写好的,没接收信号到也知道怎么办。
    信号的编号就是该数组的下标

  • 以上三个位图实现了进程内置了识别信号的方式。

在这里插入图片描述

int isHandler(int signo)
{
    if(block& signo)
    {
        //该信号被block阻塞了,根本就不看是否接收到该信号pending
    }
    else
    {
        if(signo & pending)
        {
            //该信号没有被阻塞并且已经收到了
            handler_array[singo](signo);
            return 0;
        }
    }
    return 1;
}

不要认为有接口的才算是system call,也要意识到OS也会给用户提供数据类型配合系统调用来完成,比如pid_t是fork创建子进程的返回值。

sigset_t 数据类型
 #include <signal.h>
//设置数据类型函数

       int sigemptyset(sigset_t *set);

       int sigfillset(sigset_t *set);

       int sigaddset(sigset_t *set, int signum);

       int sigdelset(sigset_t *set, int signum);

       int sigismember(const sigset_t *set, int signum);

sigset_t数据类型结构接收pending时表示信号是否被接收,接收block时表示某一个信号是否被阻塞。

虽然sigset_t 是一个位图结构,但是不同的OS实现时不一样的,不能让用户直接修改该变量需要使用特定的函数。
sigset_t set; set是变量,在哪里保存呢?都是在用户栈上

sigprocnask()设置屏蔽字修改block位图

在这里插入图片描述

sigset_t *set是输入性参数;oldset是输出型参数(返回老的信号屏蔽字-block位图)

SIG_BLOCK:  set包含了我们希望添加的当前信号屏蔽字的信号,相当mask=mask|set;
SIG_UNBLOCK:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETMAXK:设置当前信号屏蔽字为set所指向的值,相当于msk=set

读取或者更改进程的信号屏蔽字,设置屏蔽字的方式,让某些信号不被递达。

9号信号可以屏蔽吗?不可以,管理员信号是不能被屏蔽和自定义捕捉的。

实验:设置2号信号的屏蔽字

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int main()
{
  sigset_t iset,oset;
    
  sigemptyset(&iset);
  sigemptyset(&oset);
  
  sigaddset(&iset,2);
  sigprocmask(SIG_SETMASK,&iset,&oset);
  while(1)
  {
    printf("hello world\n");
    sleep(1);
  }

  return 0;
}

sigpending()处理pending位图

在这里插入图片描述

不对pending位图做出修改,只是单纯的获取进程的pending位图。显然*set只是输出型参数。

  • 那pending位图是由谁来更改的呢?OS,我们只需要知道怎么获取就行。
实验:获取pending位图

如果我的进程屏蔽掉2号信号,并且不断的获取当前进程的pending位图,并且打印显示(0000000000),然后手动发送2号信号,因为2号信号不会被递达,所以会一直保存在pending位图中,当不断获取位图时,打印的就是
在这里插入图片描述

当重新接收2时信号重新被抵达,但是你看不到由1变成0:

在这里插入图片描述

因为2号信号的默认是中止进程,就没了啊。非得看到,所以就对2号信号进行捕捉然后执行自定义函数,改变含义就行。

在这里插入图片描述

singal函数就是用来修改handler 表的

信号发送后

  • 为什么合适的时候才会处理。当前进程可能在做更重要的事情。

​ 信号延时处理(取决于OS和进程)

  • 什么是合适的时候?信号什么时候被处理?

    因为信号是被保存在进程PCB中的pending位图当中,处理包括检测和递达(忽略,默认,自定义)
    当进程从内核态返回到用户态的时候,进行上限的检测并且处理工作。

内核态和用户态

理解一:
用户态:就是用户代码和数据被访问或者执行的时候,所处的状态。我们自己写的代码全部都是在用户态执行的。
内核态:执行OS的代码和数据时,计算机所处的状态就叫做内核态,OS的代码的执行全部都是在内核态

  • 主要区别:在于权限

调用系统调用接口就是在用户和OS之间的切换,比如用户调用open()系统调用,open函数是在内核中实现的,用户调用系统函数的时候,除了进入open函数,身份也发生变化,用户身份变成了内核身份。

理解二:
用户的身份是以进程作为代表的。用户的数据和代码一定要被加载到内存,OS的代码和数据也是一定要被加载到内存中的(开机的过程)。

  • OS的代码是怎么被执行到的呢?只有一个CPU。因为存在系统级页表(内核页表),整个系统只有一份。

    CPU根据用户级页表将物理内存中的用户的代码数据加载到内存,同理CPU调用系统级别页表找到磁盘上的OS代码数据。而用户级页表人手一份,而系统级页表只有一个,为各种进程所共享。

CPU中的CR3寄存器区分OS和普通用户,为0代表OS,为3代表用户。
进程具有了地址空间,是能够看到用户和内核的所有内容的不一定能访问。

  • CPU内有寄存器保存了当前进程的状态,
    用户态使用的是用户级页表,只能访问用户数据和代码
    内核态使用的是内核级页表,只能访问内核级的代码和数据

所有进程之间无论如何进行切换,都能够保证一定找到一个OS,因为我们每个进程都有3~4GB的地址空间使用同一张内核页表,因为每个进程的地址空间都有1G的内核空间。

所谓的系统调用,就是进程的身份转化成为内核,然后根据内核页表找到系统函数执行就行了。
在大部分情况下,实际上我们OS都是可以在进程的上下文中直接执行的。

状态切换理解(何时进行信号捕捉)

用户态执行系统调用,切换到内核态身份,执行完毕之后返回的途中
要进行进程信号的检查,有没有被block 有没有被OS传递某种信号,没信号就直接返回。如果有信号,就经过三张表,一次检查是否被屏蔽,pending位图检查信号是否到达,以及默认(比如是2号中止,那么直接在现在的内核态中止进程)忽略(直接返回用户态走系统调用的下一行代码)还是自定义捕捉(根据函数地址找到自定义函数(内核态->用户态),执行完再回到内核态,然后再返回到系统调用代码)。具体情况如下:
在这里插入图片描述

  • 自定义捕捉的理解从上图来的抽象图:

在这里插入图片描述

这样实现返回时机合适的情况。

  • 为何一定要切换回用户态才能够执行信号捕捉方法?
    OS能不能直接执行别人的代码?OS不相信任何人,OS因为身份特殊不能直接执行用户的代码。如果handler方法里面写了恶意代码就直接导致了系统的崩溃。

所以合适的时候就是从内核切换回用户态的时候,对于信号的检查和处理。

信号捕捉函数sigaction();

修改的是handler函数指针数组,和signal();函数类似。

NAME
       sigaction - examine and change a signal action
SYNOPSIS
       #include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
		//							输入性参数					输出型参数

在这里插入图片描述

实验:使用sigaction进行信号捕捉

在这里插入图片描述

默认:
一个信号是不允许嵌套捕捉的。
当一个进程信号被检查执行时,会默认将他的信号设置屏蔽字,以免再执行过程中再一次调用它,形成混乱。
而你想:
当一个信号执行时,顺带着将其他信号也进行处理,比如在自定义捕捉2号信号时,也同步自定义掉3号信号,就可以将想处理的另一个信号添加到sigaction结构体中的sa_mask中,是sigset_t类型,要调用设置这个数据类型的函数。

	int main()
 35 {
 36   struct sigaction act;
 37   memset(&act,0,sizeof(act));
 38   act.sa_handler=handler;
 39   //act.sa_handler=SIG_IGN;//忽略这个2号信号
 40   sigemptyset(&act.sa_mask);
 41   sigaddset(&act.sa_mask,3);//顺便处理3号信号的自定义
 42   
 43   sigaction(2,&act,NULL);
 44   //修改的是当前进程的handler函数指针数组特定内容                                        
 45   while(1)
 46   {
 47     printf("hello xiaoawei\n");
 48     sleep(1);
 49   }

重复发送2号信号,只会记住一个,就会有信号的丢失,可以是最新的那一个。而实时信号是不会像上面一样丢失的。

可重入函数:

主执行流执行插入节点时,因为信号的到来,调用信号捕捉执行流都进行插入节点。insert函数被重复进入了。造成节点的覆盖,链接混乱问题,节点丢失,内存泄漏的问题。
如果一个函数被重入不会出现问题,叫做可重入函数
如果一个函数被重复进入会出现问题,叫做不可重入函数
我们学到的大部分函数,大部分是不可重入的。
malloc free,调用各种库的函数实现的都是不可重入的。

volatile

编译器会对程序进行优化,程序逻辑中是CPU不断对内存中的变量进行读取并且计算结果之后返回到内存当中。

因为主执行流没有对flag进行修改,优化就不将他放回内存而是放到寄存器中,这样就可以很快的下一次访问到flag。
优化的过程就是为了减少频繁访问的次数,就将内个变量的内容缓存到寄存器,以后主执行流就直接访问CPU中的缓存数据就行了。

但是,当信号调用函数对于内存中的数据进行修改之后,CPU中的并没有发生变化,CPU就忽略了信号对于内存中数据的处理。

在这里插入图片描述

当加上了volatile之后,就修正了这一问题,被寄存器屏蔽掉了。
作用就是:告诉编译器不要对我这个变量做任何优化,要贯穿式的读取内存不要读取中间缓冲区寄存器中的数据,保持内存的可见性。

SIGCHLD信号

子进程退出时,会向父进程发送SIGCHLD信号,该信号的默认处理动作是忽略。避免父进程阻塞式等待子进程的退出。

实验证明

在这里插入图片描述

我们可以在信号捕捉函数的时候可以直接回收子进程。waitpid();

void handler(int sig)
{
    pid_t id;
    while((id=waitpid(-1,NULL,WNOHANG))>0)
        //WNOHANG非阻塞,一直读,读取失败时就说明底层没有子进程需要退出了
        //-1代表任意一个子进程
        //while循环解决了多个子进程
    {
        printf("wait child success:%d\n",id);
    }
    printf("child is quit!%d\n",getpid());
}
  • 如果不想手动的设置子进程的回收,也不用给父进程发信号,你完事了就直接退出吧!也别成Z状态等待别人释放了。

    不回收子进程,我们可以对他显示设置忽略17号信号signal(SIGCHLD,SIG_IGN),当进程退出后不用父进程wait,自动释放僵尸进程,就没有僵尸状态了。不是所有的场景都需要我们等待。子进程退就退吧!只在Linux下有效。

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

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

相关文章

SSRF ME XCTF

题目 就是一个验证框和URL框&#xff0c;两个都必须有参数 解法 验证码 做一个粗略的脚本&#xff0c;一般验证码都是数字&#xff0c;所以直接开md5&#xff1a; def cmpcapt(substr):for i in range(1,100001):md5 hashlib.md5(str(i).encode(utf-8))hexmd5 md5.hexd…

机器学习任务功略

目录 机器学习的结构机器学习攻略 训练集loss较大 model bias问题optimization问题 gradient descent的问题解决 如何区分训练集loss大是model bias还是优化器的问题 测试集loss较大 overfitting过拟合 为什么会有overfitting解决过拟合的方法 训练集与测试集的不匹配 如何选择…

Linux内核基础篇——常用调试技巧汇总

文章目录printk动态输出BUG()和BUG_ON()dump_stack()devmemprintk printk共有8个等级&#xff0c;从0-7&#xff0c;等级依次降低。 打印等级可以通过修改/proc/sys/kernel/printk来改变。 查看printk等级&#xff1a; cat /proc/sys/kernel/printk 7 4 1 7打开内核所有打印…

2022圣诞树(C语言摇钱树版本)

逐梦编程&#xff0c;让中华屹立世界之巅。 简单的事情重复做,重复的事情用心做,用心的事情坚持做&#xff1b; 文章目录前言一、个人感悟二、圣诞树由来三、圣诞树发展历史演变四、常见的圣诞树种类五、摇钱圣诞树效果展示六、实现思路七、编码实现总结新壁纸前言 时光飞逝&a…

前端工程师必须掌握—《Webpack教程》

Webpack 学习视频地址 文章目录Webpack1.webpack基础1.1.初始化隔行变色的案例1.2.安装和配置webpack1.2.1.安装webpack1.2.2.配置webpack1.2.3.了解mode可选值的应用场景1.2.4.自定义打包的入口和出口2.插件2.1.安装和配置webpack-dev-server2.1.1.安装webpack-dev-server2.1…

SpringCache+Redis的整合(微服务)

缓存作用&#xff1a; 举个例子&#xff1a;在我们程序中&#xff0c;很多配置数据&#xff08;例如一个商品信息、一个白名单、一个第三方客户的回调接口&#xff09;&#xff0c;这些数据存在我们的DB上&#xff0c;数据量比较少&#xff0c;但是程序访问很频繁&#xff0c;…

Prometheus(十一)Grafana告警

主要概念和特点 关键概念或特征含义Data sources for Alerting 告警的数据源配置从哪里查询到告警信息数据Provisioning for Alerting 告警的配置使用文件等方式配置警报资源&#xff0c;已经管理警报资源Scheduler 调度器评估告警规则&#xff0c;将其视为定期对数据源运行查…

ubuntu虚拟机修改静态ip

我的是&#xff1a;ubuntu 20.04&#xff0c;所以 第一步 sudo vi /etc/netplan/01-network-manager-all.yaml第二步 gateway4已经弃用了&#xff0c;换成下面的&#xff1a; network:version: 2renderer: NetworkManagerethernets:ens33:addresses: [192.168.125.132/24]r…

内核比较: 2.6 内核中改进了内存管理

随着 Linux 内核的发展和成熟&#xff0c;更多的用户期待着 Linux 可以运行非常大的系统来处理科学分析应用程序或者海量数据库。这些企业级的应用程序通常需要大量的内存才能好好运行。2.4 Linux 内核有识别相当大数量的内存的功能&#xff0c;但是 2.5 内核发生了很多改变&am…

docker高级篇第三章-dockerfile案例之制作自己的centos镜像

在上一篇文章中《Dockerfile介绍及常用保留指令》,我们介绍了Dockerfile是什么以及Dockerfile常用的保留字段。熟悉了这些之后,有没有想自己动手写一个Dockerfile呢?本文咱们就实战自己Dockerfile。 案例需求: 我们以远程仓库的centos为模板,制作出代用vim\ifconfig\jav…

Qt实现表格树控件-自绘树节点虚线

一、开心一刻 一程序员第一次上女朋友家她妈板着脸问 &#xff1a;你想娶我女儿&#xff0c;有多少存款&#xff1f; 程序员低了下头&#xff1a;五百&#xff01; 她妈更鄙视了&#xff1a;才五百块&#xff0c;买个厕所都不够&#xff01; 程序员忙说&#xff1a;不是人民币&…

Android混淆技术综述

1. 引入 大量的恶意软件都使用了混淆技术来逃检测。查了下Android混淆技术&#xff0c;看了如下两篇资料&#xff1a; Understanding Android Obfuscation Techniques: A Large-Scale Investigation in the Wildhttps://github.com/ClaudiuGeorgiu/Obfuscapk 对Android的混淆…

logging日志管理

1.日志作用 不管是在项目开发还是测试过程中&#xff0c;项目运行一旦出现问题日志信息就非常重要了。日志是定位问题的重要手段&#xff0c;就像侦探人员要根据现场留下的线索来推断案情。 2.日志级别 脚本运行会有很多的情况&#xff0c;比如调试信息、报错异常信息等。日…

渣土车空车未盖盖识别系统 OpenCv

渣土车空车未盖盖识别系统通过OpenCvyolo网络模型实时检测路过的渣土车情况&#xff0c;发现空车未盖盖立即进行抓拍回传。OpenCV基于C实现&#xff0c;同时提供python, Ruby, Matlab等语言的接口。OpenCV-Python是OpenCV的Python API&#xff0c;结合了OpenCV CAPI和Python语言…

Android开发中的线程池使用

一、前言 既然Android中已经有了线程的概念&#xff0c;那么为什么需要使用线程池呢&#xff1f;我们从两个方面给出使用线程池的原因。 首先线程的新建和销毁都是存在性能上的消耗的&#xff0c;如果一个时间段有大量的网络请求&#xff0c;那么就需要多个线程的创建与销毁&am…

NSGA and NSGA-II

目录1 NSGA1.1 传统多目标优化方法1.2 多目标转为单目标的缺点1.3 权重向量距离说明1.4 NSGA方法1.4.1 流程1.4.2 关键步骤1.5 注意2 NSGA-II2.1 NSGA的缺点2.2 NSGA-II在NSGA上的变动2.3 NSGA-II流程1 NSGA 1.1 传统多目标优化方法 使用权重向量&#xff0c;将多目标问题转化…

Java入门练习题及其答案第一弹

Java入门练习题及其答案第一弹 文章目录Java入门练习题及其答案第一弹素数打印乘法口诀表最大公约数水仙花数二进制中1的个数二进制奇偶数位素数打印 只能被1和自己整除 import java.util.Scanner;public static void main(String[] args) {Scanner scanner new Scanner(Sys…

UI自动化测试-第一个测试脚本

前提 我们在进行UI自动化测试时&#xff0c;一般采用javaselenium或者pythonselenium的方式。由于python比较简单&#xff0c;上手快&#xff0c;因此建议大家采用pythonselenium的方式来进行UI自动化。 1、安装pycharm PyCharm是一种Python IDE&#xff08;Integrated Deve…

【OpenCV-Python】教程:7-5 理解SVM

OpenCV Python SVM 学习 【目标】 直观理解 SVM 【理论】 线性可分 下图有两种类型的数据&#xff0c;红色和蓝色。在kNN中&#xff0c;对于一个测试数据&#xff0c;我们用来测量它与所有训练样本的距离&#xff0c;并取距离最小的一个。测量所有的距离需要大量的时间&am…

计算距离春节还有多长时间

你知道距离春节&#xff0c;还剩下多少时间吗&#xff1f; 或许你已经在默默心算了。 可是&#xff0c;如果我想要精确一点的结果&#xff0c;比如精确到多少分钟、多少秒呢&#xff1f; 要怎么计算呢&#xff1f; 这里可以使用Python进行计算。 首先&#xff0c;需要导入…