linux 系统编程复习07-信号

1 复习目标

  • 了解信号中的基本概念
  • 熟练使用信号相关的函数
  • 参考文档使用信号集操作相关函数
  • 熟练使用信号捕捉函数signal
  • 熟练使用信号捕捉函数sigaction
  • 熟练掌握使用信号完成子进程的回收

信号介绍

  • 信号的概念

信号是信息的载体,Linux/UNIX 环境下,古老、经典的通信方式, 现下依然是主要的通信手段。

  • 信号在我们的生活中随处可见,例如:
  • 古代战争中摔杯为号;
  • 现代战争中的信号弹;
  • 体育比赛中使用的信号枪......
  • 信号的特点
  • 简单
  • 不能携带大量信息
  • 满足某个特点条件才会产生

2 信号的机制

进程A给进程B发送信号,进程B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕后再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。

每个进程收到的所有信号,都是由内核负责发送的。

进程A给进程B发送信号示意图:

2.1 信号的状态

信号有三种状态:产生、未决和递达。

  • 信号的产生
  • 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\
  • 系统调用产生,如:kill、raise、abort
  • 软件条件产生,如:定时器alarm
  • 硬件异常产生,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
  • 命令产生,如:kill命令
  • 未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。 
  • 递达:递送并且到达进程。

2.2 信号的处理方式

  • 执行默认动作
  • 忽略信号(丢弃不处理)
  • 捕捉信号(调用用户的自定义的处理函数)

2.3 信号的特质

信号的实现手段导致信号有很强的延时性,但对于用户来说,时间非常短,不易察觉。

Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集

注:表示PCB的task_struct结构体定义在:

  /usr/src/linux-headers-4.4.0-97/include/linux/sched.h:1390

2.4 阻塞信号集和未决信号集

    Linux内核的进程控制块PCB是一个结构体,这个结构体里面包含了信号相关的信息,主要有阻塞信号集和未决信号集。

  • 阻塞信号集中保存的都是被当前进程阻塞的信号。若当前进程收到的是阻塞信号集中的某些信号,这些信号需要暂时被阻塞,不予处理。
  • 信号产生后由于某些原因(主要是阻塞)不能抵达,这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态;若是信号从阻塞信号集中解除阻塞,则该信号会被处理,并从未决信号集中去除。

2.5 信号的四要素

  • 通过man 7 signal可以查看信号相关信息

1 信号的编号

  • 使用kill -l命令可以查看当前系统有哪些信号,不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。

2 信号的名称

3 产生信号的事件

4信号的默认处理动作

  • Term:终止进程
  • Ign:忽略信号 (默认即时对该种信号忽略操作)
  • Core:终止进程,生成Core文件。(查验死亡原因,用于gdb调试)
  • Stop:停止(暂停)进程
  • Cont:继续运行进程
  • 特别需要注意的是:The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
  • 几个常用到的信号

SIGINT、SIGQUIT、SIGKILL、SIGSEGV、SIGUSR1、SIGUSR2、SIGPIPE、SIGALRM、SIGTERM、SIGCHLD、SIGSTOP、SIGCONT

3 信号相关函数

3.1 signal函数

  • 函数作用:注册信号捕捉函数
  • 函数原型

  typedef void (*sighandler_t)(int);

        sighandler_t signal(int signum, sighandler_t handler);

  • 函数参数
  • signum:信号编号
  • handler:信号处理函数

3.2 kill函数/命令

  • 描述:给指定进程发送指定信号
  • kill命令:kill -SIGKILL 进程PID
  • kill函数原型:int kill(pid_t pid, int sig);
  • 函数返回值:
  • 成功:0;
  • 失败:-1,设置errno
  • 函数参数:
  • sig信号参数:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
  • pid参数:
  • pid > 0: 发送信号给指定的进程。
  • pid = 0: 发送信号给与调用kill函数进程属于同一进程组的所有进程。
  • pid < -1:  取|pid|发给对应进程组。
  • pid = -1:发送给进程有权限发送的系统中所有进程。

进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。

//signal函数测试---注册信号处理函数
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

//信号处理函数
void sighandler(int signo)
{
	printf("signo==[%d]\n", signo);
}

int main()
{
	//注册信号处理函数
	signal(SIGINT, sighandler);


	while(1)
	{
		sleep(1);
		kill(getpid(), SIGINT);
	}

	return 0;
}

3.3 abort函数raise函数

  • raise函数
  • 函说描述:给当前进程发送指定信号(自己给自己发)
  • 函数原型:int raise(int sig);
  • 函数返回值:成功:0,失败非0值
  • 函数拓展:raise(signo) == kill(getpid(), signo);
  • abort函数
  • 函数描述:给自己发送异常终止信号 6) SIGABRT,并产生core文件
  • 函数原型:void abort(void);  
  • 函数拓展:abort() == kill(getpid(), SIGABRT);
//raise和abort函数测-
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

//信号处理函数
void sighandler(int signo)
{
	printf("signo==[%d]\n", signo);
}

int main()
{
	//注册信号处理函数
	signal(SIGINT, sighandler);

	//给当前进程发送SIGINT信号
	raise(SIGINT);

	//给当前进程发送SIGABRT
	abort();

	while(1)
	{
		sleep(10);
	}

	return 0;
}

3.4 alarm函数

  • 函数原型:unsigned int alarm(unsigned int seconds); 
  • 函数描述:设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止每个进程都有且只有唯一的一个定时器。
  • 函数返回值:返回0或剩余的秒数,无失败。例如:
  • 常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。

alarm使用的是自然定时法,与进程状态无关,就绪、运行、挂起(阻塞、暂停)、终止、僵尸...无论进程处于何种状态,alarm都计时。

=========================================

练习题1:编写一个程序测试alarm函数

//signal函数测试---注册信号处理函数
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

//信号处理函数
void sighandler(int signo)
{
	printf("signo==[%d]\n", signo);
}

int main()
{
	//注册信号处理函数
	signal(SIGINT, sighandler);
	signal(SIGALRM, sighandler);

	int n = alarm(5);
	printf("first: n==[%d]\n", n);

	sleep(2);
	n = alarm(5);
	//n = alarm(0); //取消时钟
	printf("second: n==[%d]\n", n);

	while(1)
	{
		sleep(10);
	}

	return 0;
}

练习题2:编写程序,测试你的电脑1秒种能数多个数字。

#include<iostream>
#include<vector>
#include<unistd.h>
#include<algorithm>
using namespace std;








int main()
{

 alarm(1);
 int i=0;

 while(1){
    printf("[%d]",i++);
 }



return 0;
}

  • 使用time命令查看程序执行的时间。程序运行的瓶颈在于IO,优化程序,首选优化IO。
  • 实际执行时间 = 系统时间 + 用户时间 + 损耗时间

损耗的时间主要来来自文件IO操作,IO操作会有用户区到内核区的切换,切换的次数越多越耗时。

3.5 setitimer函数

  • 函数原型       

int setitimer(int which, const struct itimerval *new_value,

                     struct itimerval *old_value);

  • 函数描述

设置定时器(闹钟),可代替alarm函数,精度微秒us,可以实现周期定时。

  • 函数返回值
  • 成功:0;
  • 失败:-1,设置errno值
  • 函数参数:
  • which:指定定时方式
  • 自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
  • 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM  只计算进程占用cpu的时间
  • 运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间
  • new_value:struct itimerval, 负责设定timeout时间。
  • itimerval.it_value: 设定第一次执行function所延迟的秒数 itimerval.it_interval: 设定以后每几秒执行function

struct itimerval {

    struct timerval it_interval; // 闹钟触发周期

    struct timerval it_value; // 闹钟触发时间

  };

  struct timeval {

    long tv_sec; // 秒

    long tv_usec; // 微秒

 }             

  • old_value: 存放旧的timeout值,一般指定为NULL

练习: 使用setitimer实现每隔一秒打印一次hello, world

4 信号集相关

4.1 未决信号集和阻塞信号集的关系

阻塞信号集是当前进程要阻塞的信号的集合,未决信号集是当前进程中还处于未决状态的信号的集合,这两个集合存储在内核的PCB中。

  • 下面以SIGINT为例说明信号未决信号集和阻塞信号集的关系:

当进程收到一个SIGINT信号(信号编号为2),首先这个信号会保存在未决信号集合中,此时对应的2号编号的这个位置上置为1,表示处于未决状态;在这个信号需要被处理之前首先要在阻塞信号集中的编号为2的位置上去检查该值是否为1:

      • 如果为1,表示SIGNIT信号被当前进程阻塞了,这个信号暂时不被处理,所以未决信号集上该位置上的值保持为1,表示该信号处于未决状态;
      • 如果为0,表示SIGINT信号没有被当前进程阻塞,这个信号需要被处理,内核会对SIGINT信号进行处理(执行默认动作,忽略或者执行用户自定义的信号处理函数),并将未决信号集中编号为2的位置上将1变为0,表示该信号已经处理了,这个时间非常短暂,用户感知不到。

当SIGINT信号从阻塞信号集中解除阻塞之后,该信号就会被处理。

4.2 信号集相关函数

由于信号集属于内核的一块区域,用户不能直接操作内核空间,为此,内核提供了一些信号集相关的接口函数,使用这些函数用户就可以完成对信号集的相关操作。

信号集是一个能表示多个信号的数据类型,sigset_t set,set即一个信号集。既然是一个集合,就需要对集进行添加、删除等操作。

sigset_t类型的定义在signal.h文件中的第49行处:

typedef __sigset_t sigset_t;

__sigset_t的定义在sigset.h文件中的26,27行处:

# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))

  typedef struct

  {

    unsigned long int __val[_SIGSET_NWORDS];

  } __sigset_t;

上述变量类型的定义的查找有个小窍门: 可以执行gcc的预处理命令:

gcc -E test.c -o test.i 这样头文件就会展开,可以直接到test.i文件中看到相关变量类型的定义。

  • 信号集相关函数
  • int sigemptyset(sigset_t *set);

函数说明:将某个信号集清0  

函数返回值:成功:0;失败:-1,设置errno

  • int sigfillset(sigset_t *set);

函数说明:将某个信号集置1   

函数返回值:成功:0;失败:-1,设置errno

  • int sigaddset(sigset_t *set, int signum);

函数说明:将某个信号加入信号集合中

函数返回值:成功:0;失败:-1,设置errno

  • int sigdelset(sigset_t *set, int signum);

函数说明:将某信号从信号清出信号集   

函数返回值:成功:0;失败:-1,设置errno

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

函数说明:判断某个信号是否在信号集中

函数返回值:在:1;不在:0;出错:-1,设置errno

  • sigprocmask函数

函数说明:用来屏蔽信号、解除屏蔽也使用该函数。其本质,读

取或修改进程控制块中的信号屏蔽字(阻塞信号集)。

特别注意,屏蔽信号只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃处理。

  • 函数原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 函数返回值:成功:0;失败:-1,设置errno
  • 函数参数:
  1. how参数取值:假设当前的信号屏蔽字为mask
  1. SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set
  2. SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set
  3. SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于mask = set若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
        1. set:传入参数,是一个自定义信号集合。由参数how来指示如何修改当前信号屏蔽字。
        2. oldset:传出参数,保存旧的信号屏蔽字。
  • sigpending函数
  • 函数原型:int sigpending(sigset_t *set);    
  • 函数说明:读取当前进程的未决信号集
  • 函数参数:set传出参数
  • 函数返回值:成功:0;失败:-1,设置errno
//信号集相关函数测试
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

//信号处理函数
void sighandler(int signo)
{
	printf("signo==[%d]\n", signo);
}

int main()
{

	//注册SIGINT和SIGQUIT的信号处理函数
	signal(SIGINT, sighandler);
	signal(SIGQUIT, sighandler);

	//定义sigset_t类型的变量
	sigset_t pending, mask, oldmask;

	//初始化
	sigemptyset(&pending);
	sigemptyset(&mask);
	sigemptyset(&oldmask);

	//将SIGINT和SIGQUIT加入到阻塞信号集中
	sigaddset(&mask, SIGINT);
	sigaddset(&mask, SIGQUIT);

	//将mask中的SIGINT和SIGQUIT信号加入到阻塞信号集中
	//sigprocmask(SIG_BLOCK, &mask, NULL);
	sigprocmask(SIG_BLOCK, &mask, &oldmask);

	int i = 1;
	int k = 1;
	while(1)
	{
		//获取未决信号集
		sigpending(&pending);	

		for(i=1; i<32; i++)
		{
			//判断某个信号是否在集合中
			if(sigismember(&pending, i)==1)	
			{
				printf("1");
			}
			else
			{
				printf("0");	
			}
		}
		printf("\n");

		if(k++%10==0)
		{
			//从阻塞信号集中解除对SIGINT和SIGQUIT的阻塞
			//sigprocmask(SIG_UNBLOCK, &mask, NULL);	
			sigprocmask(SIG_SETMASK, &oldmask, NULL);	
		}
		else
		{
			sigprocmask(SIG_BLOCK, &mask, NULL);	
		}

		sleep(1);
	}

	return 0;
}

练习:编写程序,设置阻塞信号集并把所有常规信号的未决状态打印至屏幕。

5 信号捕捉函数

  • signal函数
  • sigaction函数
    • 函数说明:

注册一个信号处理函数

    • 函数原型:

  int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

    • 函数参数:
      1. signum:捕捉的信号
      2. act:    传入参数,新的处理方式。
      3. oldact: 传出参数,旧的处理方式

struct sigaction {

       void  (*sa_handler)(int); // 信号处理函数

       void  (*sa_sigaction)(int, siginfo_t *, void *); //信号处理函数

       sigset_t  sa_mask; //信号处理函数执行期间需要阻塞的信号

       int      sa_flags; //通常为0,表示使用默认标识

       void     (*sa_restorer)(void);

};

//sigaction函数测试---注册信号处理函数
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

//信号处理函数
void sighandler(int signo)
{
	printf("signo==[%d]\n", signo);
	sleep(4);
}

int main()
{
	//注册信号处理函数
	struct sigaction act;
	act.sa_handler = sighandler;
	sigemptyset(&act.sa_mask);  //在信号处理函数执行期间, 不阻塞任何信号
	sigaddset(&act.sa_mask, SIGQUIT);
	act.sa_flags = 0;
	sigaction(SIGINT, &act, NULL);

	
	signal(SIGQUIT, sighandler);	

	while(1)
	{
		sleep(10);
	}

	return 0;
}

  • 总结:
    • sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
    • sa_mask: 用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
    • sa_flags:通常设置为0,使用默认属性。
    • sa_restorer:已不再使用

  • 练习:编写程序,使用sigaction函数注册信号捕捉函数,并使用这个程序验证信号是否支持排队。
  • 知识点: 信号处理不支持排队:
  • 在XXX信号处理函数执行期间, XXX信号是被阻塞的, 如果该信号产生了多次, 在XXX信号处理函数结束之后,  该XXX信号只被处理一次.
  • 在XXX信号处理函数执行期间,如果阻塞了YYY信号, 若YYY信号产生了多次, 当XXX信号处理函数结束后, YYY信号只会被处理一次.

  • 内核实现信号捕捉的过程

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:

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

当前正在执行main函数,这时发生中断或异常切换到内核态。

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

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

sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。

如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

6 SIGCHLD信号

6.1 产生SIGCHLD信号的条件

  • 子进程结束的时候
  • 子进程收到SIGSTOP信号
  • 当子进程停止时,收到SIGCONT信号

6.2 SIGCHLD信号的作用

子进程退出后,内核会给它的父进程发送SIGCHLD信号,父进程收到这个信号后可以对子进程进行回收。

使用SIGCHLD信号完成对子进程的回收可以避免父进程阻塞等待而不能执行其他操作,只有当父进程收到SIGCHLD信号之后才去调用信号捕捉函数完成对子进程的回收,未收到SIGCHLD信号之前可以处理其他操作。

6.3 使用SIGCHLD信号完成对子进程的回收

  • 练习:父进程创建三个子进程,然后让父进程捕获SIGCHLD信号完成对子进程的回收。
  • 注意点:
  • //父进程使用SICCHLD信号完成对子进程的回收
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <signal.h>
    #include <sys/wait.h>
    
    void waitchild(int signo)
    {
    	pid_t wpid;
    
    	//回收子进程
    	while(1)
    	{
    		wpid = waitpid(-1, NULL, WNOHANG);
    		if(wpid>0)
    		{
    			printf("child is quit, wpid==[%d]\n", wpid);
    		}
    		else if(wpid==0)
    		{
    			printf("child is living, wpid==[%d]\n", wpid);
    			break;
    		}
    		else if(wpid==-1)
    		{
    			printf("no child is living, wpid==[%d]\n", wpid);
    			break;
    		}
    	}
    }
    
    int main()
    {
    	int i = 0;
    	int n = 3;
    
    	//将SIGCHLD信号阻塞
    	sigset_t mask;
    	sigemptyset(&mask);
    	sigaddset(&mask, SIGCHLD);
    	sigprocmask(SIG_BLOCK, &mask, NULL);
    
    	for(i=0; i<n; i++)	
    	{
    		//fork子进程
    		pid_t pid = fork();
    		if(pid<0) //fork失败的情况
    		{
    			perror("fork error");
    			return -1;
    		}
    		else if(pid>0) //父进程
    		{
    			printf("father: fpid==[%d], cpid==[%d]\n", getpid(), pid);
    			sleep(1);
    		}
    		else if(pid==0) //子进程
    		{
    			printf("child: fpid==[%d], cpid==[%d]\n", getppid(), getpid());
    			break;
    		}
    	}
    
    	//父进程
    	if(i==3)
    	{
    		printf("[%d]:father: fpid==[%d]\n", i, getpid());
    		//signal(SIGCHLD, waitchild);
    		//注册信号处理函数
    		struct sigaction act;
    		act.sa_handler = waitchild;
    		sigemptyset(&act.sa_mask);
    		act.sa_flags = 0;
    		sleep(5);
    		sigaction(SIGCHLD, &act, NULL);
    
    		//解除对SIGCHLD信号的阻塞
    		sigprocmask(SIG_UNBLOCK, &mask, NULL);
    
    		while(1)
    		{
    			sleep(1);
    		}
    	}
    
    	//第1个子进程
    	if(i==0)
    	{
    		printf("[%d]:child: cpid==[%d]\n", i, getpid());
    		//sleep(1);
    	}
    
    	//第2个子进程
    	if(i==1)
    	{
    		printf("[%d]:child: cpid==[%d]\n", i, getpid());
    		sleep(1);
    	}
    
    	//第3个子进程
    	if(i==2)
    	{
    		printf("[%d]:child: cpid==[%d]\n", i, getpid());
    		sleep(1);
    	}
    
    	return 0;
    }
    

  • 有可能还未完成信号处理函数的注册三个子进程都退出了。
  • 解决办法:可以在fork之前先将SIGCHLD信号阻塞,当完成信号处理函数的注册后在解除阻塞。
  • SIGCHLD信号函数处理期间, SIGCHLD信号若再次产生是被阻塞的,而且若产生了多次, 则该信号只会被处理一次, 这样可能会产生僵尸进程。
  • 解决办法: 可以在信号处理函数里面使用while(1)循环回收, 这样就有可能出现捕获一次SIGCHLD信号但是回收了多个子进程的情况,从而可以避免产生僵尸进程。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【计算机网络】三次握手 四次挥手

目录 1.三次握手 2.四次挥手 3.总结 三次握手和四次挥手是有连接特有的。三次握手&#xff0c;四次挥手指的是TCP有连接特点的中的步骤。建立连接(三次握手)&#xff0c;断开连接(四次挥手)。建立连接操作一般都是客户端主动发起&#xff0c;断开连接操作客户端和服务器都可…

Linux shell编程学习笔记17:for循环语句

Linux Shell 脚本编程和其他编程语言一样&#xff0c;支持算数、关系、布尔、字符串、文件测试等多种运算&#xff0c;同样也需要进行根据条件进行流程控制&#xff0c;提供了if、for、while、until等语句。 之前我们探讨了if语句&#xff0c;现在我们来探讨for循环语句。 Li…

rem设置 vscode设置rem 适配 px转rem

1、下载安装 2、 二、 如果代码里面设置 就按代码里面来 -- 20 代码: // 基准大小 const baseSize 20 // 设置 rem 函数 function setRem() {// 当前页面宽度相对于 750 宽的缩放比例&#xff0c;可根据自己需要修改。const scale document.documentElement.clientWidth / …

学会吃亏,也是善良

《六祖坛经》上说&#xff1a;一切福田&#xff0c;都离不开心地。 心田上播下善良的种子&#xff0c;总有一天&#xff0c;会开花结果。 所以&#xff0c;心地善良是一种福祉&#xff0c;是对生命最好的感恩与回报&#xff0c;心存善念&#xff0c;便是最好的修行&#xff01;…

【Java每日一题】——第四十三题:编程用多态实现打印机.。分为黑白打印机和彩色打印机,不同类型的打印机打印效果不同。(2023.10.30)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

RK3568-适配at24c04模块

将at24c04模块连接到开发板i2c2总线上 i2ctool查看i2c2总线上都有哪些设备 UU表示设备地址的从设备被驱动占用,卸载对应的驱动后,UU就会变成从设备地址。at24c04模块设备地址 0x50和0x51是at24c04模块i2c芯片的设备地址。这个从芯片手册上也可以得知。A0 A1 A2表示的是模块对…

【鸿蒙软件开发】ArkTS基础组件之Select(下拉菜单)、Slider(滑动条)

文章目录 前言一、Select下拉菜单1.1 子组件1.2 接口参数 1.3 属性1.4 事件1.5 示例代码 二、Slider2.1 子组件2.2 接口参数&#xff1a;SliderStyle枚举说明 2.3 属性2.4 事件SliderChangeMode枚举说明 2.5 示例代码 总结 前言 Select组件&#xff1a;提供下拉选择菜单&#…

地球系统模式CESM

目前通用地球系统模式&#xff08;Community Earth System Model&#xff0c;CESM&#xff09;在研究地球的过去、现在和未来的气候状况中具有越来越普遍的应用。CESM由美国NCAR于2010年07月推出以来&#xff0c;一直受到气候学界的密切关注。近年升级的CESM2.0在大气、陆地、海…

Linux高级命令(扩展)

一、find命令 1、find命令作用 在Linux操作系统中&#xff0c;find命令主要用于进行文件的搜索。 2、基本语法 # find 搜索路径 [选项 选项的值] ... 选项说明&#xff1a; -name &#xff1a;根据文件的名称搜索文件&#xff0c;支持*通配符 -type &#xff1a;f代表普通文…

基于小安派AiPi-Eye-S1的Nes游戏机

1.作品展示 作品功能可见以下B站视频 外壳可以使用灰太狼大佬提供的外壳STL文件。在嘉立创三维猴上打印&#xff08;外壳12元快递6元&#xff09;。 外壳从以下的帖子中获取&#xff1a; 模型分享 2.作品说明 2.1 硬件部分 硬件上使用到了AiPi-Eye-S1开发板以及3.5寸 240*3…

ElasticSearch快速入门实战

全文检索 什么是全文检索 全文检索是一种通过对文本内容进行全面索引和搜索的技术。它可以快速地在大量文本数据中查找包含特定关键词或短语的文档&#xff0c;并返回相关的搜索结果。全文检索广泛应用于各种信息管理系统和应用中&#xff0c;如搜索引擎、文档管理系统、电子…

安防监控项目---CGI接口的移植和使用

文章目录 前言一、CGI二、CGI的具体移植步骤2.1 cgi源码下载2.2 搭建交叉编译环境2.3 注意事项 三、测试结果总结 前言 书接上期&#xff0c;上期与大家分享的是boa服务器的移植&#xff0c;那么几天要和大家介绍的呢是一款接口&#xff0c;哈哈哈&#xff0c;用起来也是有点难…

NSS刷题 js前端修改 os.path.join漏洞

打算刷一遍nssweb题&#xff08;任重道远&#xff09; 前面很简单 都是签到题 这里主要记录一下没想到的题目 [GDOUCTF 2023]hate eat snake 这里 是对js的处理 有弹窗 说明可能存在 alert 我们去看看js 这里进行了判断 如果 getScore>-0x1e9* 我们结合上面 我觉得是6…

人工智能基础_机器学习008_使用正规方程_损失函数进行计算_一元一次和二元一次方程演示_sklearn线性回归演示---人工智能工作笔记0048

自然界很多都是正态分布的,身高,年龄,体重...但是财富不是. 然后我们来看一下这个y = wx+b 线性回归方程. 然后我们用上面的代码演示. 可以看到首先import numpy as np 导入numby 数据计算库 import matplotlib.pyplot as plt 然后导入图形画的库 然后: X = np.linspace(0,…

文件正在使用,操作无法完成。windows查看占用文件的程序

查看占用 tasklist /m IDMShellExt64.dll 映像名称 PID 模块explorer.exe 7452 IDMShellExt64.dll杀死进程 taskkill /f /PID 7452 成功: 已终止 PID 为 7452 的进程。重启explorer explorer

20.3 OpenSSL 对称AES加解密算法

AES算法是一种对称加密算法&#xff0c;全称为高级加密标准&#xff08;Advanced Encryption Standard&#xff09;。它是一种分组密码&#xff0c;以128比特为一个分组进行加密&#xff0c;其密钥长度可以是128比特、192比特或256比特&#xff0c;因此可以提供不同等级的安全性…

OpenCV—自动驾驶实时道路车道检测(完整代码)

自动驾驶汽车是人工智能领域最具颠覆性的创新之一。在深度学习算法的推动下,它们不断推动我们的社会向前发展,并在移动领域创造新的机遇。自动驾驶汽车可以去传统汽车可以去的任何地方,并且可以完成经验丰富的人类驾驶员所做的一切。但正确地训练它是非常重要的。自动驾驶汽…

计算机考研 | 2013年 | 计算机组成原理真题

文章目录 【计算机组成原理2013年真题43题-9分】【第一步&#xff1a;信息提取】【第二步&#xff1a;具体解答】 【计算机组成原理2013年真题44题-14分】【第一步&#xff1a;信息提取】【第二步&#xff1a;具体解答】 【计算机组成原理2013年真题43题-9分】 某32位计算机&a…

sql-50练习题6-10

sql练习题6-10题 前言数据库表结构介绍学生表课程表成绩表教师表 0-6 查询"李"姓老师的数量0-7 查询学过"李四"老师授课的同学的信息0-8 查询没学过"李四"老师授课的同学的信息0-9 查询学过编号为"01"并且也学过编号为"02"的…

SMART PLC梯形速度曲线轨迹规划(追剪从轴控制)

在介绍本专栏之前,大家可以参考另一篇博图PLC的梯形加减速点动功能块介绍文章 梯形加减速点动功能块(博途SCL)_RXXW_Dor的博客-CSDN博客文章浏览阅读184次。SMART PLC斜坡函数SMART PLC斜坡函数功能块(梯形图代码)_RXXW_Dor的博客-CSDN博客斜坡函数Ramp的具体应用可以参看下…