Day04 Liunx高级系统设计4-信号

news2024/12/30 1:40:21

进程间通讯

引入

如何将 A 进程中的数据传入 B 进程呢 ?
我们要使用进程间通讯

概述

中文名 : 进程间通讯
英文名 :IPC
英文全称 :Inter Processes Communication
作用:
数据传输:一个进程需要将他的数据发送给另一个进程】
资源共享:多个进程可以共享同一个资源
通知事件:一个时间需要给另一个进程或另一个组发送信号,告诉它发生了什么事
进程控制:有些进程希望完全控制另一个进程的执行(如debug),此时控制进程希望拦截另一个进程的所有操作,并能够及时知道它的改变状态。
发展史:
最初的 UNIX 进程间通信
SYSTEM V 进程间通信
POSIX 进程间通信 (POSIX:Portable Operating System interface 可移植操作系
统接口)
Socket 进程间通信
Linux 把优势都继承了下来并形成了自己的 IPC
Linux 进程间通信机制

分类

1.信号量

2.管道

3.消息队列

4.socket(套接字)

5.内存共享

信号

概述

信号是Linux进程间通信的最古老方式

特点

        简单

        不能携带大量信息

        满足某个特设条件才发出。

注意 :
         信号是一种异步通信方式。
        信号是软件中断,它是在软件层次上对中断机制的一种模拟。
        信号可以导 致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某
一个突发事件。
        进程不必等待信号的到达,进程也不知道信号什么时候到达。
        信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。
名词解释
同步 : 事件、操作或进程是有序的 , 一个操作必须在另一个操作完成后开始执行。
异步 : 事件、操作或进程是独立的 , 可以在不等待其他操作完成的情况下开始执行
中断机制 : 是现代计算机系统中的基本机制之一,完成了对计算机各个事件(如时钟、
键盘等)响应工作
硬件中断 : 是由硬件设备触发的中断,如时钟中断、串口接收中断、外部中断等。当硬件设备有数据或事件需要处理时,会向CPU 发送一个中断请求, CPU 在收到中断请求后,会立即暂停当前正在执行的任务,进入中断处理程序中处理中断请求。硬件中断具有实时性强、可靠性高、处理速度快等特点。
软件中断 : 是由软件程序触发的中断,如系统调用、软中断、异常等。软件中断不是由硬件设备触发的,而是由软件程序主动发起的,一般用于系统调用、进程切换、异常处理等任务。软件中断需要在程序中进行调用,其响应速度和实时性相对较差,但是具有灵活性和可控性高的特点。

完整的信号周期

信号的注册

信号的产生

信号的处理函数

信号的注销

注意:这里的信号的注册,产生,注销是信号的内部机制,不是信号的函数实现

信号的编号

每个信号的名字都以字符 SIG 开头。
每个信号和一个数字编码相对应,在头文件 signum.h 中,这些信号都被定义为 正整
数。
信号名定义路径: /usr/include/i386-linux-gnu/bits/signum.h
Linux 下,要想查看这些信号和编码的对应关系,可使用命令: kill -l
不存在编号为 0 的信号。
1-31 号信号称之为常规信号 ( 也叫普通信号 , 标准信号 )
34-64号信号称为实时信号(自定义信号)
通过 man 7 signal 查看信号帮助文档
注意:9)SIGKILL和19)SIGSTOP信号不允许忽略和捕捉,只能执行默认操作。(重要)
SIGQUIT信号使用 ctrl + ‘\’来触发

信号的产生

1, 当用户按某些终端键时,将产生信号。 终端上按 “Ctrl+c” 组合键通常产生中断信号 SIGINT 终端上按 “Ctrl+\” 键通常产生中断信号 SIGQUIT 终端上按 “Ctrl+z” 键通常产生中断信号 SIGSTOP 等。
2, 硬件异常将产生信号。 除数为 0 ,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。
 
3, 软件异常将产生信号。 当检测到某种软件条件已发生 ( 如:定时器 alarm) ,并将其通知有关进程时,产生信号。
4, 调用系统函数 ( 如: kill raise abort) 将发送信号。
注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
5, 运行 kill/killall 命令将发送信号。 此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。

kill函数(他杀)

作用 : 给指定进程发送指定信号 ( 不一定杀死 ), 他杀
语法
所需头
#include <sys/types.h>
#include <signal.h>
函数
int kill(pid_t pid, int sig);
参数 :
pid : 取值有 4 种情况 :
pid > 0: 将信号传送给进程 ID 为 pid 的进程。
pid = 0 : 将信号传送给 当前进程所在进程组中的所有进程。
pid = -1 : 将信号传送给 系统内所有的进程。
pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill - l("l" 为字母 ) 进行相应查看。不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
返回值 :
成功: 0
失败: -1
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    int pid = fork();
    if(pid == 0)
    {
        //进入子进程
        while(1)
        {
            printf("子进程%u在玩游戏\n",getpid());
            sleep(1);
        }
        _exit(-1);
    }
    else if(pid > 0)
    {
        //进入父进程
        printf("给你三秒钟去写作业\n");
        sleep(3);
        kill(pid,SIGKILL);
        wait(NULL);
        printf("%d被%d干掉了\n",pid,getpid());
    }
    return 0;
}

raise函数(自杀)

作用:给当前进程自己发指定信号,等价于kill(getpid(),sig),自杀

语法:

所需头:

        #include <signal.h>

函数:

        int raise(int sig);

参数:

        sig:信号编号

返回值:

        成功:0

        失败:非0

示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    int i = 0;
    while (1)
    {
        sleep(1);
        printf("好无聊,想去死%d\n",i);
        if(i == 3)
        {
            printf("死给你看\n");
            raise(SIGKILL);
        }
        i++;
    }
    wait(NULL);
    return 0;
}

abort函数(自杀)

作用:给自己发送异常终止信号(6)SIGABRT,并产生core文件,等价于kill(getpid(),sig),自杀

core 文件 ( 了解 )
core 就是内核
core 文件会包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息还有各种
函数调用堆栈信息等,我们可以理解为是程序工作当前状态存储生成第一个文件,许多
的程序出错的时候都会产生一个 core 文件,通过工具分析这个文件,我们可以定位到程
序异常退出的时候对应的堆栈调用等信息,找出问题所在并进行及时解决。
语法:
头文件:
        #include <signal.h>
函数:
        void abort(void)
注意:
        1. 使用 abort 函数结束进程 , 进程结束前会刷新缓冲区并关闭所有的文件描述符( 0/1/2
        
        2,即使 SIGABRT 信号被加入阻塞集,一旦进程调用了 abort 函数,进程也还是会被终止
示例:
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    char myfo[3] = {"我要发信号了","我真的要发信号了","拜拜了您呐"};
    int i = 0;
    while (1)
    {
        char *p = myfo[i];
        printf("%s\n",p);
        sleep(1);
        i++;
        if(i == 3)
        abort();
    }
    
    return 0;
}

alarm函数(自杀)

作用:

设置定时器 ( 闹钟 ) 。在指定时间后 ( 单位秒 ) ,内核会给当前进程发送 14 ) SIGALRM 信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
取消定时器 alarm(0) ,返回旧闹钟余下秒数。
函数:
所需头文件:
        #include <unistd.h>
函数:
        unsigned int alarm(unsigned int seconds);
参数:
        seconds  单位:秒
返回值:
        若以前没有设置过定时器,或设置的定时器已超时,返回0;
        否则返回定时器剩余的秒数,并重新设定定时器。
注意:
定时,与进程状态无关 ( 即自然定时法 )! 就绪、运行、挂起 ( 阻塞、暂停 ) 、终止、僵 …… 无论进程处于何种状态, alarm 都计时
不阻塞当前进程
示例;
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/time.h>
int main(int argc, char const *argv[])
{
    int seconds = 0;
    seconds = alarm(5);
    printf("计时器开始执行,seconds=%d\n",seconds);
    sleep(3);
    seconds = alarm(5);
    printf("计时器开始执行seconds=%d\n",seconds);
    while(1);
    return 0;
}

setitimer函数(计时器)

作用:

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

语法
所需头文件:
        #include <sys/time.h>
函数:
        
        int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数 :
        which:指定定时器类型,可以是以下三个值之一:
        a) 自然定时: ITIMER_REAL → 14 SIGALRM 计算自然时间
        
        b) 虚拟空间计时 ( 用户空间 ) ITIMER_VIRTUAL → 26 SIGVTALRM 只计算进程占用 cpu 的时间
        c) 运行时计时 ( 用户 + 内核 ) ITIMER_PROF → 27 SIGPROF 计算占用 cpu及执行系统调用的时间
        new_value:一个指向 struct itimerval 结构体的指针,用于指定新的定时器值(启动时间和间隔时间)。
        old_value:一个指向 struct itimerval 结构体的指针,用于获取旧的定时器值,即之前设置的定时器值。如果不需要获取旧的定时器值,则可以传入NULL
struct itimerval 结构体
struct itimerval {
struct timerval it_interval; // 间隔时间
struct timerval it_value; // 延迟时间
};
struct timerval 结果体
struct timeval {
long tv_sec; //
long tv_usec; // 微秒
};
示例:
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
void fun()
{
    printf("文件已被处理\n");
}
int main(int argc, char const *argv[])
{
    struct itimerval new_a;
    new_a.it_value.tv_sec = 5;
    new_a.it_value.tv_usec = 0;
    new_a.it_interval.tv_sec = 1;
    new_a.it_interval.tv_usec = 0;
    signal(SIGALRM,fun);
    setitimer(ITIMER_REAL,&new_a,NULL);
    while(1);
    return 0;
}

pause函数

作用:

将调用进程挂起直至捕捉到信号为止。这个函数通常用于判断信号是否已到

语法:

头文件:

        #include <unistd.h>

函数:

        int pause(void)

返回值:

        知道捕获到信号,pause函数才返回-1,且errno被设置成EINTR

示例:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void fun()
{
    printf("进程已被执行\n");
}
int main(int argc, char const *argv[])
{
    printf("进程一开始,当前进程是%d\n",getpid());
    signal(SIGALRM,fun);
    alarm(3);
    int x = pause();
    printf("文件结束%d\n",x);
    return 0;
}

信号处理

方式

一个进程收到一个信号的时候,可以用如下方法进行处理:
1 SIG_DFL 执行系统默认动作 对大多数信号来说,系统默认动作是用来终止该进程。
2 SIG_IGN 忽略此信号 ( 丢弃 ) 接收到此信号后没有任何动作。
3 )信号处理函数名 : 执行自定义信号处理函数 ( 捕获 ) 用用户定义的信号处理函数处理
该信号。
【注意】: SIGKILL SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种
使进程终止的可靠方法
捕捉信号并且信号信号的处理方式有两个函数 ,signal sigaction
signal 函数
作用:
注册信号处理函数(不可用于 SIGKILL SIGSTOP 信号),即确定收到信号后处理函
数的入口地址。此函数不会阻塞
函数
所需头文件
        #include <signal.h>
函数 :
// 定义的一个函数指针
        typedef void(*sighandler_t)(int);
//signal 函数
        sighandler_t signal(int signum, sighandler_t handler);
参数 :
        signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过
命令 kill - l("l" 为字母 ) 进行相应查看。
handler : 取值有 3 种情况:
        SIG_IGN:忽略该信号
        SIG_DFL:执行系统默认动作
        信号处理函数名:自定义信号处理函数
返回值 :
        成功:第一次返回 NULL ,下一次返回此信号上一次注册的信号处理函数的地址。
        如果需要使用此返回值,必须在前面先声明此函数指针的类型。
        失败:返回 SIG_ERR
示例:
示例 : 自定义 ctrl+c 信号处理 1
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void myfun()
{
    printf("自定义函数\n");
    _exit(0);
}
int main(int argc, char const *argv[])
{
    signal(SIGINT,myfun);
    while(1);

    return 0;
}

示例 : 自定义 ctrl+c 信号处理 2
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
char *p;
void fun(int sigcode)
{
    if(p != NULL)
    {
        free(p);
        
    }
    printf("p空间被释放了\n");
    _exit(0);
}
int main(int argc, char const *argv[])
{
    p =malloc(50);
    strcpy(p,"helloSIG");
    signal(SIGINT,fun);
    while(1)
    {
        printf("%s\n",p);
        sleep(1);

    }
    return 0;
}

注意
signal 函数由 ANSI 定义 , 由于历史原因在不同版本的 Unix 和不同版本的 Linux
可能有不同的行为。因此应该尽量避免使用它 , 取而代之使用 sigaction 函数
sigaction 函数
作用 : 检查或修改指定信号的设置(或同时执行这两种操作)。
函数
所需头文件 :
#include <signal.h>
函数 :
int sigaction(int signum, const struct sigaction *act, struct sigaction* oldact);
参数 :
        signum:要操作的信号。
        act: 要设置的对信号的新处理方式(传入参数)。
        oldact:原来对信号的处理方式(传出参数)。
注意 :
如果 act 指针非空 , 则要改变指定信号的处理方式 ( 设置 ), 如果 oldact 指针非空 , 则系统将此前指定信号的处理方式存入oldact
返回值 :
        成功:0
        失败:-1
struct sigaction:结构体

struct sigaction

{

    void (*sa_handler)(int);                        // 旧的信号处理函数指针

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

    sigset_t sa_mask;                               // 信号阻塞集

    int sa_flags;                                   // 信号处理的方式

    void (*sa_restorer)(void);                      // 已弃用

};

/*

1,sa_handler,sa_sigaction:信号处理函数指针,和signal()里的函数指针用法

一样,应根据情况给 sa_sigaction、sa_handler 两者之一赋值,其取值如下:

a SIGIGN:忽略该信号

b SIGDFL:执行系统默认动作

c 处理函数名:自定义信号处理函数

2,sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。

3,sa_flags:用于指定信号处理的行为,通常设置为 0,表使用默认属性。它可

以是以下值的“按位或”组合:

SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)

SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD

信号。

SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这

时子进程如果退出也不会成为僵尸进程。

SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这

个信号。

SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理

函数

void(*sa_sigaction)(int signum, siginfo_t *info, void *context);

参数说明:

signum:信号的编号。

info:记录信号发送进程信息的结构体。

context:可以赋给指向ucontext_t类型的一个对象的指针,以引用在传递信

号时被中断的接收进程或线程的上下文。

*/

示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
void fun(int sigcode)
{
    printf("ctrl+c的信号编码是:%d\n",sigcode);
    _exit(-1);
}
int main(int argc, char const *argv[])
{
    struct sigaction cat;
    cat.sa_handler = fun;
    sigaction(SIGINT,&cat,NULL);
    while(1);
    return 0;
}

示例2::发出定义信号

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
void fun(int sigcode)
{
    printf("SIGUSR1得信号编号是:%d\n",sigcode);
    _exit(-1);
}
int main(int argc, char const *argv[])
{
    struct sigaction cat;
    cat.sa_handler = fun;
    sigaction(SIGUSR1,&cat,NULL);
    kill(getpid(),SIGUSR1);
    while(1);
    return 0;
}

可重入函数

概念
函数可以同时被很多进程调用,并且保证每个模块都能得到期望的结果
注意
1 、不使用或返回 静态的数据、全局变量(除非用信号量互斥)。
2 、不调用动态内存分配、释放的函数。
3 、不调用任何不可重入的函数(如标准 I/O 函数)
常见的可重入函数列表
示例:
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void show(int pid)
{
    for(int i = 0;i < 3;i++)
    {
        char buf[50] = {0};
        sprintf(buf,"进程%d被%d次执行\n",pid,i,"\r\n");
        write(1,buf,sizeof(buf));
        sleep(1);
    }
}
int main(int argc, char const *argv[])
{
    for (int i = 0; i < 5; i++)
    {
        int pid = fork();
        if(pid == 0)
        {
            printf(getpid());
            _exit(-1);
        }
        else if(pid > 0)
        {
            printf("子进程%d被创建\n",pid);
            _exit(-1);
        }
    }
    while(1)
    {
        int id = waitpid(-1,NULL,WNOHANG);
        if(id > 0)
        {
            printf("子进程在被回收\n",id);
            sleep(1);
        }
        else if(id == 0)
        {
            break;
        }
    }
    return 0;
}

信号集

概述

PCB 中有两个非常重要的信号集。一个称之为 阻塞信号集 ,另一个称之为 未决信号集” 。 这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改
信号阻塞集:将某些信号加入 集合,对他们设置屏蔽,当屏蔽 x 信号后,再收到该信号,该信号的处理将暂缓。
未决信号集:未被处理的信号集合。

自定义信号集函数

所需头文件
        #include <signal.h>
函数
        int sigemptyset(sigset_t *set); //将 set 集合置空
        int sigfillset(sigset_t *set); // 将所有信号加入 set 集合
        int sigaddset(sigset_t *set, int signo); //将 signo 信号加入到 set 集合
        int sigdelset(sigset_t *set, int signo); //从 set 集合中移除 signo 信号
        int sigismember(const sigset_t *set, int signo); //判断信号是否存在于集合中
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
//定义一个信号集
sigset_t set;
//清空set集
sigemptyset(&set);
//将SIGINT添加到set集合中
sigaddset(&set, SIGINT);
//将SIGTSTP添加到set集和中
sigaddset(&set, SIGTSTP);
if (sigismember(&set, SIGINT))
{
printf("SIGINT是在set集合中\n");
}
else
{
printf("SIGINT不在set集合中\n");
}
//将SIGINT从集合中删除
sigdelset(&set, SIGINT);
if (sigismember(&set, SIGINT))
{
printf("SIGINT是在set集合中\n");
}
else
{
printf("SIGINT不在set集合中\n");
}
return 0;
}

信号阻塞集

概述
信号阻塞集也称信号屏蔽集、信号掩码。
每个进程都有一个阻塞集,创建子进程时子进程将继承父进程的阻塞集。
信号阻塞集用来描述哪些信号递送到该进程的时候被阻塞(在信号发生时记住它 , 直到进程准备好时再将信号通知进程)。 所谓阻塞并不是禁止传送信号, 而是暂缓信号的传送。若将被阻塞的信号从信号阻塞集中删除, 且对应的信号在被阻塞时发生了,进程将会收到相应的信号。
我们可以通过 sigprocmask() 修改当前的信号阻塞集来改变信号的阻塞情况。
sigprocmask 函数
作用
检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
函数
所需头文件
#include <signal.h>
函数
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数
how: 信号阻塞集合的修改方法 , 3 种情况 :
SIG_BLOCK: 添加
向信号阻塞集合中添加 set 信号集 , 新的信号掩码是 set 和旧信号掩码的并集. 相当于 mask=mask|set
SIG_UNBLOCK: 删除
从信号阻塞集合中删除 set 信号集 , 从当前信号掩码中去除 set 中的信号 . 相当于mask=mask&~set
SIG_SETMASK: 替换
将信号阻塞集合设为 set 信号集 , 相当于原来信号阻塞集的内容清空 , 然后按照set 中的信号重新设置信号阻塞集。相当于 mask=set
set: 要操作的信号集地址。若 set NULL, 则不改变信号阻塞集合 , 函数只把当前信号阻塞集合保存到oldset
oldset: 保存原先信号阻塞集地址
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
//定义一个信号集
//vscode中编写的sigset_t会报错,不管
sigset_t set;
//清空set集
sigemptyset(&set);
//将SIGINT添加到set集合中
sigaddset(&set, SIGINT);
//将set集合添加到阻塞集
//vscode中编写的SIG_BLOCK,SIG_BLOCK会报错不管
sigprocmask(SIG_BLOCK, &set, NULL);
printf("SIGINT信号将在5秒后从阻塞集中删除\n");
sleep(5);
sigprocmask(SIG_UNBLOCK, &set, NULL);
while (1);
return 0;
}

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

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

相关文章

排序:直接选择排序

直接选择排序&#xff1a; 本质&#xff1a; 直接选择排序的本质就是在数组中进行遍历挑选出最大的元素&#xff0c;讲最大的元素放到对应的位置后&#xff0c;再次选出次大的位置&#xff0c;而后又放到对应的位置..........................直到数组成为一个有序序列。 优…

二叉树的层平均值[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[3.00000,14.50000,1…

解决:AttributeError: module ‘copy’ has no attribute ‘copy’

解决&#xff1a;AttributeError: module ‘copy’ has no attribute ‘copy’ 文章目录 解决&#xff1a;AttributeError: module copy has no attribute copy背景报错问题报错翻译报错位置代码报错原因解决方法方法一方法二方法三今天的分享就到此结束了 背景 在使用之前的代…

C语言指针详解上

1 野指针 int main01(){//野指针就是没有初始化的指针,指针的指向是随机的,不可以 操作野指针//int a 0;//指针p保存的地址一定是定义过的(向系统申请过的)int *p;//野指针*p 200;printf("%d\n",*p);system("pause");return 0;}2 空指针 空指针的作用…

Unity 关于Ray、RaycastHit、Raycast及其使用

Unity中&#xff0c;我们要进行物理模拟和碰撞检测时&#xff0c;有三个重要的概念Ray、RaycastHit、Raycast。 其中&#xff0c;Ray可以理解为射线&#xff0c;它是一条从起点沿着特定方向延伸的无限长线段。 它的语法是&#xff1a; Ray(Vector3 origin, Vector3 directio…

使用阿里巴巴同步工具DataX实现Mysql与ElasticSearch(ES)数据同步

一、Linux环境要求 二、准备工作 2.1 Linux安装jdk 2.2 linux安装python 2.3 下载DataX&#xff1a; 三、DataX压缩包导入&#xff0c;解压缩 四、编写同步Job 五、执行Job 六、定时更新 6.1 创建定时任务 6.2 提交定时任务 6.3 查看定时任务 七、增量更新思路 一、Linux环境要…

el-table操作栏按钮过多 增加展开/收起功能

是的 如图所示有那么一条数据 列表操作栏的按钮七八个 小屏笔记本啥数据项也别看了 就剩下个固定列大刺刺的占着整个页面 解决方法&#xff1a; <el-table-column :width"tableToggle ? 600 : 300" label"操作栏" align"center" header-ali…

类和对象,this指针

一、类的引入&#xff1a; 如下&#xff0c;在C中&#xff0c;我们可以在结构体中定义函数&#xff0c;如下&#xff0c;之前我们学习C中中一直是在结构体中定义变量。 struct student{void studentinfo(const char* name,const char* gener,int age){ strcpy(_name,name);st…

前端css面试题(四)

文章目录 对一些 CSS 默认值的考察css选择器说一下z-indexz-index的值大的dom一定能覆盖z-index值小的dom吗如果一个第三方组件的z-index与我们现有业务的页面有冲突&#xff0c;怎么处理关于浮动元素样式引入权重问题链接引入&#xff08;Link&#xff09;和 import注入的区别…

理解输出电压纹波和噪声:来源与抑制

医疗设备、测试测量仪器等很多应用对电源的纹波和噪声极其敏感。理解输出电压纹波和噪声的产生机制以及测量技术是优化改进电路性能的基础。 1&#xff1a;输出电压纹波 以Buck电路为例&#xff0c;由于寄生参数的影响&#xff0c;实际Buck电路的输出电压并非是稳定干净的直流…

【wvp】测试记录

ffmpeg 这是个莫名其妙的报错&#xff0c;通过排查&#xff0c;应该是zlm哪个进程引起的 会议室的性能 网络IO也就20M

【分布式微服务专题】从单体到分布式(二、SpringCloud整合Nacos)

目录 前言阅读对象阅读导航前置知识笔记正文一、下载安装二、项目整合2.1 服务注册与发现2.2 动态配置管理 三、其他实验四、服务之间的调用 学习总结感谢 前言 本篇笔记主要是记录我整合Nacos项目进来的过程。以实现服务注册发现&#xff0c;以及分布式配置管理。关于Nacos&a…

Leetcode刷题笔记题解(C++):LCR 121. 寻找目标值 - 二维数组

思路&#xff1a;从左小角或者右上角开始遍历&#xff0c;假设右上角开始遍历&#xff0c;如果当前值大于目标值则列-1&#xff1b;如果当前值小于目标值则行1&#xff0c;以此遍历来查找目标值&#xff1b;注意col和row的选取 class Solution { public:bool findTargetIn2DPl…

测试文档---消息驿站

文章目录 项目背景测试计划服务器模块设计测试用例进行单元测试/黑盒测试 客户端模块设计测试用例进行单元测试/黑盒测试 转发规则模块设计测试用例进行单元测试/黑盒测试 测试总结 项目背景 在高并发量的情况下&#xff0c;针对某一台服务器的访问量激增就可能导致该服务器“…

关于最长上升子序列的动态规划问题的优化算法(二分搜索)

最长递增子序列 暴力解法&#xff1a; 思路&#xff1a;使用动态规划的思想&#xff0c;判断当前元素之前的所有元素&#xff0c;如果比当前元素小&#xff0c;则修改当前元素的最长递增子序列&#xff08;需判断是否需要修改&#xff09;。 时间复杂度&#xff1a;O(n^2) im…

leetcode做题笔记1466. 重新规划路线

n 座城市&#xff0c;从 0 到 n-1 编号&#xff0c;其间共有 n-1 条路线。因此&#xff0c;要想在两座不同城市之间旅行只有唯一一条路线可供选择&#xff08;路线网形成一颗树&#xff09;。去年&#xff0c;交通运输部决定重新规划路线&#xff0c;以改变交通拥堵的状况。 路…

C#winform根据选择的Excel文件在数据库中创建数据表

C#winform根据选择的Excel文件在数据库中创建数据表 需求&#xff1a;根据选择的Excel文件在数据库中创建数据表&#xff1b;可以实现特殊字段&#xff08;比如字段中含有数字、下划线、特殊字符等&#xff09;以及表名创建 C#实现 using System; using System.Data; using S…

运维05:自动化

人工运维时代 运维人员早期需要维护众多的机器&#xff0c;因此需要执行很多重复的劳动&#xff0c;很多机器需要同时部署相同的服务或者是执行相同的命令&#xff0c;还得反复地登录不同的机器&#xff0c;执行重复的动作 自动化运维时代 早期运维人员会结合ssh免密登录&…

Shell数组函数:数组——数组和循环(二)

for脚本快速定义数组 [rootlocalhost ~]# vim for12.sh #脚本编辑 #!/bin/bash for a in cat /etc/hosts do hosts[o]$a donefor i in ${!hosts[]} do echo "$i : ${hosts[$a]}" done[rootlocalhost ~]# vim for12.sh #执行脚本区别 &#xff1a;for的空格分割…

漏洞复现-某教育视频云平台前台某接口文件上传漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…