4.进程相关 2

8.内存映射

8.1 内存映射相关定义

创建一个文件,将保存在磁盘中的文件映射到内存中,后期两个进程之间对内存中的数据进行操作,大大减少了访问磁盘的时间,也是一种最快的 IPC ,因为进程之间可以直接对内存进行存取

image-20211113112315578

8.2 内存映射相关的系统调用

代码地址:lesson25

8.2.1 mmap & munmap 函数–内存的映射与释放

1.API

image-20211113120051909 image-20211113120531666

mmap : 将一个文件映射到内存中

这个函数返回的是内存映射出来的内存首地址

munmap : 解除一个文件和内存的映射

内存映射和管道不一样,内存映射不会发生阻塞

2.代码

通过内存映射的方法实现两个关系进程之间的通信

①导入相应的库文件

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>

②将一个文件映射到内存区

int fd = open("test.txt",O_RDWR);
int size = lseek(fd,0,SEEK_END); // 获取文件大小:从index0 到 index_end
// 创建映射
void* ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0); // prot :内存映射区域的权限,一般读写都有;flag:本地文件是否和盘文件同步
if(ptr==MAP_FAILED){
  perror("mmap");
  exit(0);
}

③父进程:等待子进程写入数据并读出来;子进程:将数据写入到内存中

// 创建子进程
pid_t pid = fork();
if(pid>0){ // 父进程读取文件
    wait(NULL); // 等待子进程写完内存映射区后再去读文件
    char buf[64];
    strcpy(buf,(char*)ptr); //拷贝字符串
    printf("read data:%s\n",buf);
}else if(pid==0){
    // 子进程向映射区写入数据
    strcpy((char*)ptr,"xuboluo");

}

④关闭映射

munmap(ptr,size);

3.代码演示

最后可以看到这里在对内存映射时文件中的文字是进行覆盖的

image-20211113123424599

8.2.2 mmap & munmap 内存的映射与释放 中常见问题

image-20211113125008844

可以对其 ++ 操作更改内存映射的开始地址,这样在写文件时就可以从指定的位置开始写

但是在进行释放内存时要传入正确的释放地址,也就是一开始系统给分配的地址

image-20211113125229391

映射的权限要小于等于文件 open 时的权限

3.

image-20211113130902882

文件偏移量的作用就是:指明从文件的哪个地方开始映射,如下图:

image-20211113125555251

一般页面大小为 4K ,虚拟内存空间的大小也为 4K

image-20211113130019986
image-20211113130436495

映射的文件大小必须要 >0 ,这样在分配虚拟内存时才能保证能分配到空间。可以使用相应的方法扩展内存

image-20211113130733355

创建映射区时会对文件描述符进行一个拷贝,即使文件被关闭也不会有影响

image-20211113131017747

8.2.3 使用内存映射实现文件拷贝

代码保存在 lesson25 copy.c 中

1.基本思路

image-20211113131247503

现在硬盘中有两个文件 A 和 B ,A 文件和 B 文件分别映射到内存当中,并获得相应的 map_ptr ,两个 ptr 之间进行通讯

image-20211115103234035

2.代码实现

#include <string.h>
#include <stdlib.h>

int main(){
    // 1.得到两个文件的文件描述符
    int fd_a = open("a.txt",O_RDWR);
    int fd_b = open("b.txt",O_RDWR | O_CREAT,0664);
    // 2.得到文件大小
    int len = lseek(fd_a,0,SEEK_END);
    // 对新建的文件进行扩展
    truncate("b.txt",len);
    write(fd_b," ",1);
    // 3.得到文件映射
    int* ptr_a = mmap(NULL,len,PROT_WRITE | PROT_READ,MAP_SHARED,fd_a,0);
    int* ptr_b = mmap(NULL,len,PROT_WRITE | PROT_READ,MAP_SHARED,fd_b,0);
    //4.内存拷贝
    memcpy(ptr_b,ptr_a,len); // 目标文件,源文件,长度
    // 5.释放资源
    munmap(ptr_a,len);
    munmap(ptr_b,len);
    // 6.关闭两个文件流
    close(fd_a);
    close(fd_b);
    return 0;
}

3.代码演示

b.txt 文件原本是不存在的空文件,拷贝后有了 a.txt 的内容

image-20211115111459381

image-20211115111316090

8.2.4 文件的匿名映射

代码保存在 lesson25 mmap-anon.c

有时候不存在真正的文件实体,不需要文件这个中介进行通信。比如说进程将读到的内容直接传递给子进程

文件的匿名映射只能存在于有关系的两个进程之间

1.代码实现

创建一个父进程将读到的 string 中的信息直接传递给子进程。关键代码是需要在 mmap 映射时添加匿名映射的属性

MAP_ANONYMOUS
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(){
    // 1.定义内存映射区的大小。以往都是根据文件大小来
    int len = 4096;
    void* ptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);

    // 2.父子间进程通信
    pid_t pid = fork();
    if(pid>0){
        // 父进程向内存缓冲区写入内容
        strcpy((char*)ptr,"hello world");
        wait(NULL);
    }else if(pid ==0){
        // 子进程读取内存映射的内容
        sleep(1); // 先等父进程写完
        printf("%s\n",(char*)ptr);
    }
    //3.释放映射
    int ret = munmap(ptr,len);
    return 0;
}

2.代码演示

最后可以看到虽然没有创建文件但还是实现了两个进程之间的通信

image-20211115114929119

9.信号

9.1 信号的基本概念

1.定义

软件中断就是指当有另一个进程到达时此进程暂停当前任务去执行另一个进程的任务。等待另一个进程执行完毕再返回到刚才的任务继续执行,是一种异步通信方式。

信号 == 软件中断

image-20211115120011525

2.中断发生的场景

  • 对于前台进程用户输入了特殊的符号比如 Ctrl+c ,这时就向进程发了一个中断信号
  • 硬件发生异常,硬件检测到一个错误条件并通知内核,随即硬核再发送相关信号通知进程。比如执行了一条异常的机器指令或者被 0 除,或者访问了一块无法访问的内存区域
  • 系统状态的变化,定时器到期引起 SIGALRM 信号,进程执行的 CPU 时间超限或者该进程的某个子进程退出
  • 执行 kill 命令或者调用 kill 函数

信号主要是软中断,告诉另一个进程可以执行了,所以说信号(软中断)也是一种进程通讯的方式

3.信号特点

简单,不能携带大量信息,满足某个特定条件才发送,优先级高

9.2 信号介绍

使用 kill -l 就可以看到所有的信号

前 31 个为常规信号,其余为实时信号

image-20211115121613069

只看红色部分

image-20211115121641643 image-20211115121700250 image-20211115121729367 image-20211115121751672

-9 SIGKILL

SIGCHLD:子进程结束时,父进程会收到这个信号

下图中的 Core 文件是指我们在终止进程时对于中止进程保存的相关信息

image-20211115122917978
man 7 signal // 查看信号指令

可以发现有的信号用三个数字表示,这是因为不同的系统架构造成的,我们在使用这些信号时选择中间的数字即可

SIGKILL and SIGSTOP 不能被捕捉,不能被阻塞,也不能被忽略

image-20211115122434497

9.2.1 Core 文件保存中断中间变量

2.代码

这里定义一个会报错的代码,定义一个 string ,但是不将这个 string 进行初始化,直接对其赋值,那么最终也会导致一个 “段错误” 的出现


3.代码演示

这里会爆出一个段错误,主要是因为没有进行一个争取的段地址映射而产生的

image-20211115124210107

4.添加 Core 文件

首先使用下面的指令查看和更改 Core 文件的大小,并且看到系统分配的 core 文件大小为 0

ulimit -a
image-20211115124835872

使用下面代码更改生层的 Core 文件大小

ulimit -c 1024 // 定义 Core 文件的大小为 1024
image-20211115125045580

因为我的代码没有显示对应的效果,所以这里用了视频中的演示

image-20211115130718582 image-20211115130835667

我们通过 gdb 的方式对 core 文件进行查看,里面说他是在执行 a.out 文件时产生的,执行了进程的终止并且是因为段错误发送了 SIGSEGV 信号

image-20211115130920338

9.3 发送信号的相关函数

image-20211115123141496

9.3.1 Kill–给指定进程发送信号

1.API

image-20211115132612396

2.代码

实现 kill -9 操作

关键代码:

kill(pid, SIGINT);
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<unistd.h>

int main(){
        pid_t pid = fork();
    if(pid == 0) {
        // 子进程
        int i = 0;
        for(i = 0; i < 5; i++) {
            printf("child process\n");
            sleep(1);
        }

    } else if(pid > 0) {
        // 父进程
        printf("parent process\n");
        sleep(2);
        printf("kill child process now\n");
        kill(pid, SIGINT);
    }
        return 0;
}

3.代码演示

image-20211115134817081

9.3.2 raise --给当前进程发送信号

1.API

man 3 raise 中查看相关文档

这个函数可以由 kill 函数实现

image-20211115132806197

9.3.3 abort–杀死当前进程

1.API

这个函数可以由 kill 函数实现

image-20211115132909178

9.3.4 alarm 定时器–在一定的时长之后发送信号

1.API

定时器只能设置一次,后面再设置定时器还是按照第一次的时间进行倒计时

而且不管进程是什么状态 alarm 都会执行

image-20211115135754550

2.代码

设置一个定时器

#include<stdio.h>
#include<unistd.h>
int main(){
        int seconds = alarm(5); // 设置一个 5 s 是定时器
        sleep(2);
        seconds = alarm(1); // 这个闹钟无效
        printf("second=%d\n",seconds); // 3

        while(1){
        }
        return 0;
}

3.代码演示

虽然在 5 秒的闹钟后加闹钟加快,但是最后得到的闹钟还是 3 秒的剩余时间,也就是按照第一次闹钟定时

image-20211115140736980

4.计数器代码实现

计数器实现电脑 1s 可以数多少个数

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

int main(){
    alarm(1); // 1s 之后杀死自己
    int i = 0;
    while(1){
        printf("%i\n",i++);
    }
    return 0;
}
image-20211115141644689

5.程序的运行时间

程序真正的运行时间 = 内核时间+用户时间(程序真正的运行时间)+消耗的时间(比如文件 IO 操作打开关闭消耗的时间)

9.3.5 setitimer–实现周期性定时

1.API

这个定时器可以根据周期设置在多少时间之后启动,每隔多少秒启动一次

image-20211116104853148

2.代码

关键代码:

如何定义结构体 new_value

int ret = setitimer(ITIMER_REAL,&new_value,NULL);

#include<sys/time.h>
#include<stdlib.h>
#include<stdio.h>
int main(){
    // 定义结构体属性参数
    struct itimerval new_value;
    // 过3秒后每隔2秒进行一次定时
    new_value.it_interval.tv_sec=2; // 每隔 2s 唤醒一次定时器
    new_value.it_interval.tv_usec = 0;
    new_value.it_value.tv_sec = 3; // 3 秒之后在设置这个定时器
    new_value.it_value.tv_usec = 0;

    // 设置一个周期定时器
    int ret = setitimer(ITIMER_REAL,&new_value,NULL);
    printf("定时器开始了");

    if(ret==-1){
        perror("setitimer");
        exit(0);
    }
    getchar(); // 防止进程提前终止
    return 0;
}

3.代码实现

首先是先打印:定时器开始了

在 3s 之后打印 Alarm clock

image-20211116093952705

9.4信号捕捉函数

9.4.1 signal 捕捉信号

1.API

在定义信号之前定义捕捉信号的函数

捕捉到信号,然后执行自己想执行的事情

image-20211116095343888

在形参中需要传入一个 sighandler ,通过前面定义发现这是一个回调函数

image-20211116095837103

2.代码

signal(SIGALRM,SIG_IGN);  // 忽略信号

3.代码演示

忽略信号:

刚才使用 setitimer 定义了一个信号,这里使用 signal 对信号忽略,可以看到它就一直在要求用户输入的地方,不会终止程序

image-20211116103347686

默认信号执行:

时隔3s后执行 Alarm clock

image-20211116103708238

执行回调函数:

在 3s 之后 signal 捕捉到第一个信号;然后每隔 2s 就会捕捉到另一个信号,捕捉到信号后执行相应的 handler 函数。执行完 handler 函数后程序继续执行下一个信号的发送(并没有终止程序)

image-20211116104502130

9.4.2 信号集的捕捉

1.API

(1)什么是信号集

PCB 中有两个信号集:阻塞信号集和未决信号集。我们可以借助信号集操作函数对这两个信号集进行操作

阻塞信号集:

将某些信号加⼊集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(处理发⽣在解除屏蔽后)

未决信号集:

这个信号还没有被处理。信号产⽣,未决信号集中描述该信号的位⽴刻翻转为1,表示信号处于未决状态。当信号被处理对应位翻转回为0。这⼀时刻往往⾮常短暂。

(2)如何处理信号

根据信号集中的值处理信号,1 代表这个信号被阻塞不能执行(未决),0 代表信号可以抵达,未决信号集中的值根据阻塞信号集中信号的变化而变化

e.g. : 当前的处理的信号 index 为 2 也就是 SIGINT 信号:

阻塞信号集中的值为 0 ,未决信号集看到阻塞信号集他的值为 0 ,也就是说不阻塞,则未决信号集中的值也变成 0

阻塞信号集中的值为 1 ,未决信号集看到阻塞信号集他的值为 1 ,先暂时将这个信号进行挂起,直到信号不阻塞了,未决信号的值再变为0

如果有更多的阻塞信号到达只能被丢弃

image-20211116112326615

信号集

(3)操控信号集的常见函数

image-20211116113405201
#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); // 判断信号是否存在

信号集的数据结构

sigset_t

2.代码

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int main(){
    // 1.定义一个信号集
    sigset_t set;
    // 2.清空信号集中的内容
    sigemptyset(&set);
    // 3. 判断信号是否还在信号集 set 中,即它是否被阻塞
    int ret = sigismember(&set,SIGINT);
    if(ret==1){
        printf("设置阻塞成功\n");
    }else if(ret==0){
        printf("设置阻塞失败\n");
    }
    // 4. 添加几个信号到信号集中
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGQUIT);
    // 5.判断添加的信号是否在信号集中
    ret = sigismember(&set,SIGINT);
    if(ret==1){
        printf("被阻塞了\n");
    }else if(ret==0){
        printf("没有被阻塞\n");
    }
    return 0;
}

3.代码演示

根据上面的代码可以看出,我们先对信号集中的信号进行清空,然后查看信号集中的成员。然后再设置信号再次查看信号集中的成员

image-20211116121318103

9.4.3 sigaction—信号捕捉函数

代码保存在 lesson26 sigaction.c 中

1.API

类似于 signal 函数捕捉信号

#include <signal.h>
/**
* 检查或修改指定信号的设置(或同时执⾏这两种操作).
* @param signum 要操作的信号.
* @param act 要设置的对信号的处理⽅式(传⼊参数).
* @param oldact:一般不用:原来对信号的处理⽅式(传出参数) . *
* 如果 act 指针⾮空,则要改变指定信号的处理⽅式(设置),
* 如果 oldact 指针⾮空,则系统将此前指定信号的处理⽅式存⼊ oldact.
*
* @return 成功: 0; 失败: -1.
*/
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

数据结构:

struct sigaction {
void(*sa_handler)(int); // 信号处理函数指针
void(*sa_sigaction)(int, siginfo_t *, void *); // 一般不用:新的信号处理函数指针
sigset_t sa_mask; // 临时信号阻塞集,在信号捕捉函数执行过程中,临时阻塞某些信号
int sa_flags; // 使用哪个信号处理函数处理信号,0:sa_handler,SA_SIGINFO :sa_sigaction
void(*sa_restorer)(void); //已弃⽤
};

sigaction 讲解

2.代码

首先设置一个定时器不断的发送信号,然后使用 sigaction 捕捉信号,捕捉到信号后执行相应的操作,操作结束后程序继续执行

关键代码:

定义信号捕捉函数,重点在于如何定义结构体变量

// 定义 act
struct sigaction act;
act.sa_flags = 0;// 使用 handelr 进行处理
act.sa_handler = my_alarm;
sigemptyset(&act.sa_mask); // 清空临时信号集
// 注册信号捕捉函数
sigaction(SIGALRM,&act,NULL);

捕捉到信号后进行的操作

// 捕捉信号后处理的函数
void my_alarm(int num){
    printf("捕捉到一个信号%d\n",num);
    printf("进行相应的处理\n");

}

3.代码演示

image-20211117113446070

9.4.5 信号捕捉流程及特性

1.流程

image-20211117114739079

(1)程序在 main 函数中执行,运行到某一行时发现需要中断然后程序就运行到内核

(2)内核使用 do_signal 函数处理递送的信号

(3)内核判断信号处理函数是否是用户自定义的,如果是用户自定义的函数则再返回到用户区执行相应的信号处理函数

(4)信号处理函数处理完成后调用 sigreturn 返回到内核

(5)从内核再返回用户区,继续刚才没有执行完的 main 程序执行

2.特性

(1)内核中有一个阻塞信号集,信号捕捉时也有一个临时信号集,当信号捕捉结束后会返回到系统中的阻塞信号集

(2)当两个信号同时发生,后面的阻塞信号要等前面的阻塞信号处理完毕才能接着处理

(3)阻塞的常规信号是不支持排队的,因为信号集的个数只能由 01表示,其他来的阻塞信号要全部被丢弃

9.4.6 SIGCHLD 信号

代码保存在 lesson26 sigchld.c 中

1.子进程什么时候会向父进程发送 SIGCHLD 信号

子进程会向父进程发送 SIGCHLD 信号,以下三种情况发生时子进程就会想父进程发送 SIGCHLD 信号,默认情况下父进程会忽略该信号

image-20211117120333156

1.子进程终止时

2.代码

首先注册阻塞信号集,并将其添加到系统阻塞信号集中。其次开启子进程,子进程执行相应方法。当子进程执行完毕后父进程对子进程进行回收,回收完毕接着执行自己的代码

调用相应的库文件:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>

注册信号阻塞集,将其添加到系统信号阻塞集中

// 提前设置好阻塞信号集阻塞 SIGCHLD ,有可能因为子进程结束父进程还没有注册阻塞信号集就没有办法阻塞子进程
sigset_t set; // 定义信号集
sigemptyset(&set); // 将信号集中的信号清空
sigaddset(&set,SIGCHLD); // 添加对子进程阻塞的监听
sigprocmask(SIG_BLOCK,&set,NULL); // 将定义的阻塞信号集添加到系统的阻塞信号集中

信号处理函数,即回收子进程

void my_handler(int num){

  printf("捕捉到的信号:%d\n",num);
  // 回收子进程 pcb 资源,防止有漏掉的子进程
  while(1){
    int ret = waitpid(-1,NULL,WNOHANG); // 进程回收
    if(ret>0){
      printf("child die ,pid=%d\n",ret);
    }else if(ret==0){ // 还有子进程没有完全回收
      break;
    }else if(ret==-1){ // 完全回收
      break;
    }
  }
}

创建子进程

// 创建子进程
pid_t pid;
for(int i=0;i<5;i++){
    pid = fork();
    if(pid==0) break;
}

父进程不断执行,并不断监听子进程发出的 SIGCHLD 信号

if(pid>0){
		// 捕捉子进程死亡时 SIGCHLD 信号
		struct sigaction act;
		act.sa_flags = 0;
		act.sa_handler = my_handler;
		sigemptyset(&act.sa_mask); // 将临时阻塞进程集设置为空
		sigaction(SIGCHLD,&act,NULL);
		// 注册完信号捕捉后解除阻塞
		sigprocmask(SIG_UNBLOCK,&set,NULL);

		while(1){ // 父进程保持执行状态
			printf("parent process pid:%d\n",getpid());
			sleep(2);
		}
	}

子进程的相关函数

else if(pid==0){
		printf("child process pid:%d\n",getpid());
	}

3.代码演示

image-20211117134750357

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

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

相关文章

第十二届蓝桥杯省赛真题(C/C++大学B组)

目录 #A 空间 #B 卡片 #C 直线 #D 货物摆放 #E 路径 #F 时间显示 #G 砝码称重 #H 杨辉三角形 #I 双向排序 #J 括号序列 #A 空间 #include <bits/stdc.h> using namespace std;int main() {cout<<256 * 1024 * 1024 / 4<<endl;return 0; } #B 卡片…

JVM性能监控与调优——命令行工具

文章目录 1、概述2、jps:查看正在运行的Java进程3、jstat&#xff1a;查看JVM统计信息4、jinfo&#xff1a;实时查看和修改JVM配置参数5、jmap&#xff1a;导出内存映像文件和内存使用情况6、jhat:JDK自带堆分析工具7、jstack&#xff1a;打印JVM中线程快照8、jcmd&#xff1a;…

第一个Swift程序

要创建第一个Swift项目,请按照以下步骤操作: 打开Xcode。如果您没有安装Xcode,可以在App Store中下载并安装它。在Xcode的欢迎界面上,选择“Create a new Xcode project”(创建新Xcode项目)。在模板选择界面上,选择“App”(应用程序)。在应用模板选择界面上,选择“Si…

【静态分析】静态分析笔记01 - Introduction

参考&#xff1a; BV1zE411s77Z [南京大学]-[软件分析]课程学习笔记(一)-introduction_南京大学软件分析笔记-CSDN博客 ------------------------------------------------------------------------------------------------------ 1. program language and static analysis…

【JavaWeb】Jsp基本教程

目录 JSP概述作用一个简单的案例&#xff1a;使用JSP页面输出当前日期 JSP处理过程JSP 生命周期编译阶段初始化阶段执行阶段销毁阶段案例 JSP页面的元素JSP指令JSP中的page指令Include指令示例 taglib指令 JSP中的小脚本与表达式JSP中的声明JSP中的注释HTML的注释JSP注释 JSP行…

llinux进程控制

学习进程创建,fork/vfork 学习到进程等待 学习到进程程序替换, 微型shell&#xff0c;重新认识shell运行原理 学习到进程终止,认识$? fork函数 在linux中fork函数时非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程…

PostgreSQL入门到实战-第十五弹

PostgreSQL入门到实战 PostgreSQL数据过滤(八)官网地址PostgreSQL概述PostgreSQL中LIKE命令理论PostgreSQL中LIKE命令实战更新计划 PostgreSQL数据过滤(八) 如何使用PostgreSQL LIKE运算符基于模式查询数据。 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一…

Commitizen:规范化你的 Git 提交信息

简介 在团队协作开发过程中&#xff0c;规范化的 Git 提交信息可以提高代码维护的效率&#xff0c;便于追踪和定位问题。Commitizen 是一个帮助我们规范化 Git 提交信息的工具&#xff0c;它提供了一种交互式的方式来生成符合约定格式的提交信息。 原理 Commitizen 的核心原…

人工智能分类算法概述

文章目录 人工智能主要分类算法决策树随机森林逻辑回归K-均值 总结 人工智能主要分类算法 人工智能分类算法是用于将数据划分为不同类别的算法。这些算法通过学习数据的特征和模式&#xff0c;将输入数据映射到相应的类别。分类算法在人工智能中具有广泛的应用&#xff0c;如图…

每日一题(leetcode2529):正整数和负整数的最大计数--二分法

因为需要O&#xff08;logn&#xff09;的复杂度&#xff0c;所以考虑使用二分法&#xff0c;先找到负数里面的最大下标&#xff08;初始值定为-1&#xff09;&#xff0c;再找到第一个正数的下标&#xff08;初始值定为数组长度值&#xff09;。最后求出个数并进行比较即可。 …

操作系统的基础知识:操作系统的特征:并发,共享,虚拟,异步

操作系统的特性&#xff1a; 1.并发 并发:指两个或多个事件在同一时间间隔内发生。这些事件宏观上是同时发生的&#xff0c;但微观上是交替注意&#xff1a;并行:指两个或多个事件在同一时刻同时发生。 操作系统的并发性指计算机系统中“同时”运行着多个程序&#xff0c;这…

【Entity Framework】聊聊EF中键

【Entity Framework】聊聊EF中键 文章目录 【Entity Framework】聊聊EF中键一、概述二、配置主键2.1 约定配置主键2.2 单个属性配置为实体主键2.3 组合主键 三、主键名称四、键类型和值五、备用键 一、概述 键用作每个实体实例的唯一标识符。EF中的大多数实体都有一个键&#…

langchain-chatchat加载Azure Open AI

1.找到knowledge_base_chat.py文件中的get_ChatOpenAI函数 2.按crtl进入get_ChatOpenAI函数位置 3.注释原先的get_ChatOpenAI函数&#xff0c;修改成以下内容&#xff1a; def get_ChatOpenAI(model_name: str,temperature: float,streaming: bool True,callbacks: List[Ca…

5款最值得推荐的电脑监控软件丨高人气甄选

在企业和学校等场所&#xff0c;电脑监控软件被广泛应用于员工或学生的行为管理。 通过监控软件&#xff0c;管理者可以了解员工或学生的学习和工作情况&#xff0c;及时发现并纠正不当行为&#xff0c;提高工作效率和学习效果。同时&#xff0c;这类软件还可以用于保护企业机…

13 指针(上)

指针是 C 语言最重要的概念之一&#xff0c;也是最难理解的概念之一。 指针是C语言的精髓&#xff0c;要想掌握C语言就需要深入地了解指针。 指针类型在考研中用得最多的地方&#xff0c;就是和结构体结合起来构造结点(如链表的结点、二叉树的结点等)。 本章专题脉络 1、指针…

SQL注入的其他攻击思路方法与Python脚本设计思路

SQL注入的其他攻击思路方法与Python脚本设计思路 也是很早就写了&#xff0c;也备个份吧 注意&#xff1a;在接下来的攻击方式中&#xff0c;由于实现的条件较为苛刻&#xff0c;并且需要较高权限&#xff0c;有的师傅又称之为高权限攻击 利用文件读取进行SQL注入 上一篇文章提…

mysql8主从复杂原理分析

MySQL 复制&#xff08;Replication&#xff09; 是官方提供的主从复制&#xff08;源到副本的复制&#xff09;方案&#xff0c;用于将一个 MySQL 的实例同步到另一个实例中。 这是使用最广泛的容灾方案&#xff08;重点掌握&#xff09;。 复制&#xff08;Replication&…

编译器如何理解C++的指针和引用?

初学引用时&#xff0c;往往很难真正理解引用&#xff0c;它与指针究竟有什么区别和联系。下面我们不妨看看编译器如何理解引用和指针的。 一.函数通过指针传参 1.1 示例代码 #include <iostream>using namespace std;void swap(int *x,int *y)//指针传参 {int tmp;t…

【机器学习300问】66、ReLU激活函数相对于Sigmoid和Tanh激活函数的优点是什么?ReLU它有局限性吗?如何改进?

一、ReLU相对于Sigmoid和Tanh的优点 &#xff08;1&#xff09;计算效率高 ReLU函数数学形式简单&#xff0c;仅需要对输入进行阈值操作&#xff0c;大于0则保留&#xff0c;小于0则置为0。Sigmoid和Tanh需要指数运算但ReLU不需要。所以相比之下它会更快&#xff0c;降低了神经…

agi入门-大模型开发基础

AGI(Artifical General Inteligence)的到来还有多久&#xff1f; 乐观预测&#xff1a;明年主流预测&#xff1a;3-5年悲观预测&#xff1a;10年 AGI时代&#xff0c;AI无处不在&#xff0c;相关从来者将如何分&#xff1f; AI使用者&#xff1a;使用别人开发的AI产品AI产品…