当前位置: 首页 > news >正文

一文让你玩转Linux多进程开发

Linux多进程开发

主要介绍多进程开发时的要点

进程状态转换

进程反应了进程执行的变化。

进程的状态分为三种 ,运行态,阻塞态,就绪态

在五态模型中分为以下几种,新建态,就绪态,运行态,阻塞态,终止态。

进程状态.png

运行态:进程占用处理器正在运行。

就绪态:进程已具备运行的条件,等待系统分配处理器运行。

阻塞态 :又称为等待(wait)态,或睡眠(sleep)态,指进程不具备运行条件,正在等待事件的完成。

新建态:进程已被创建,还未加入就绪队列。

进程相关命令

查看进程

ps aux / ajx

实时查看进程状态

top

杀死进程

kill
kill -l 列出所有信号
kill -9 进程id
killall 根据进程名杀死进程
kill -SICKILL  进程 id
​
​

进程创建

系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成 进程树结构模型

pid_t fork(void);//pid_t为int类型,进行了重载
pid_t getpid();// 获取当前进程的 pid 值。
pid_t getppid(); //获取当前进程的父进程 pid 值。

通过系统调用获取进程标识符

进程id: (PID)

父进程id: (PPID)

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
using namespace std;
​
int main()
{printf("pid: %d\n", getpid());printf("ppid: %d\n", getppid());
}

通过系统调用创建进程 fork

fork 返回值

fork的返回值会返回两次,一次是父进程,一次是子进程

在父进程中返回子进程的id

子进程返回0

通过 fork的返回值来判断子进程和父进程

fork创建进程失败会返回 -1 并设置 error

总结 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
using namespace std;
​
int main()
{pid_t pid = fork();if (pid > 0){printf("父进程 id = %d , 子进程 ppid = %d\n", getpid(), getppid());// 当前是父进程 , 返回创建子进程的进程号}else{// 当前是子进程printf("子进程 id = %d , ppid = %d\n", getpid(), getppid());}
​for (int i = 0; i < 3; i++){printf("i = %d \n", i);}
}

exec函数族

exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容。换句话说就是在进程内部调用一个可执行文件

#include <unistd.h>
extern char **environ;
​
int execl(const char *path, const char *arg, ...); // 可执行文件的路径,文件名 ,参数
int execlp(const char *file, const char *arg, ...);// 会到环境变量里面查找可执行文件
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

返回值:只有调用错误或调用失败则会返回-1,并设置error

execl.cpp

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
using namespace std;
​
int main()
{pid_t pid = fork();if (pid > 0) {
​// 父进程cout << "我是父进程" << ' ' << getpid()<<endl;}else if (pid == 0) {// 子进程execl("hello", "hello", NULL);cout << "i am chird process" << endl;}
​for (int i = 0; i < 3; i++) {cout <<"i = "<<i <<"pid = " <<getpid()<< endl;}
​
}

我们使用的比较多的是前两个函数,按函数声明使用即可

进程退出、孤儿进程、僵尸进程

93b7bc5766f14b05961ab3378fa9fbbc.png

1.进程退出

void exit(int status)  // 标准C库函数
void _exit(int status) // Linux系统标准库函数

status:进程退出码

_exit是属于 POSIX 系统调用,适用于 UNIX 和 Linux 系统。调用该系统调用后会导致当前进程直接退出,且函数不会返回。内核会关闭该进程打开的文件描述符,若还存在子进程,则交由1号进程领养,再向进程的父进程发送 SIGCHLD 信号。

返回值:无


2.孤儿进程

父进程已经结束了运行,子进程还未停止运行,这样的进程就称为孤儿进程

每当出现一个孤儿进程的时候,系统会把孤儿进程的父进程设置为init

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){//childprintf("I am child, pid : %d\n", getpid());sleep(10);}else{//parentprintf("I am parent, pid: %d\n", getpid());sleep(3);exit(0);}return 0;
}

3.僵尸进程

每个进程结束后,都会去释放自己用户区的资源;内核区的PCB无法释放,需要父进程去释放

进程终止后,父进程尚未进行回收,子进程残留资源(PCB)存放在内核中,变成僵尸进程。

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>int main(){pid_t pid;// 循环创建子进程while (1){pid = fork();if (pid < 0){perror("fork error:");exit(1);}else if (pid == 0){printf("I am a childprocess.\nI am exiting.\n");// 子进程退出,成为僵尸进程exit(0);}else{// 父进程休眠20s继续创建子进程sleep(20);continue;}}return 0;
}

以上代码就是一个典型的僵尸进程

使用命令杀死僵尸进程

grep -v grep | cut -c 5-10 | xargs kill -9

wait 函数

C 语言中的 wait 函数

wait 函数是符合 POSIX 标准的系统调用的封装器,定义在 <sys/wait.h> 头文件中。该函数用于等待子进程的程序状态变化,并检索相应的信息。wait 通常在创建新子进程的 fork 系统调用之后调用。wait 调用会暂停调用程序,直到它的一个子进程终止。

用户应该将代码结构化,使调用进程和子进程有两条不同的路径。通常用 if...else 语句来实现,该语句评估 fork 函数调用的返回值。注意 fork 在父进程中返回子进程 ID,一个正整数,在子进程中返回 0。如果调用失败,fork 将返回-1

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main()
{pid_t pid;// 创建5个子进程for (int i = 0; i < 5; i++){pid = fork();if (pid == 0){break;}}if (pid > 0){// 父进程while (1){printf("parent, pid = %d\n", getpid());// int ret = wait(NULL);int st;int ret = wait(&st);if (ret == -1){break;}if (WIFEXITED(st)){// 是不是正常退出printf("退出的状态码:%d\n", WEXITSTATUS(st));}if (WIFSIGNALED(st)){// 是不是异常终止printf("被哪个信号干掉了:%d\n", WTERMSIG(st));}printf("child die, pid = %d\n", ret);sleep(1);}}else if (pid == 0){// 子进程while (1){printf("child, pid = %d\n", getpid());sleep(1);}exit(0);}return 0; // exit(0)
}

waitpid函数

#include <sys/types.h> 
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);

功能:回收指定进程号的子进程,可以设置是否阻塞

参数 : -pid

参数功能
pid<0某个子进程的id
pid = 0回收当前进程组的所有进程
pid = -1回收所有子进程相当于 wait
pid < -1回收某个进程组的id

返回值

valuereturn value
> 0返回子进程的id
=0WNOHANG表示还要子进程或者
-1错误,没有子进程了
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{pid_t pid, childpid;int status = 0;pid = fork();if (pid < 0)printf("Error occured on forking.\n");else if (pid == 0)    //子进程{sleep(3);   //换成30s,然后kill -9 子进程pidexit(0);}   else                    //父进程{//返回后继续执行父进程的代码段}printf("pid:%d\n",pid); //打印子进程iddo{childpid = waitpid(pid, &status, WNOHANG);if (childpid == 0){printf("No child exited,ret = %d\n", childpid);sleep(1);}} while (childpid == 0);if (WIFEXITED(status))printf("正常退出:%d\n",childpid);if(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)printf("被SIGKILL信号结束\n");
}

进程间通信

进程是一个独立的资源分配单元,不同进程之间的资源是独立的;没有关联,不能在一个进程中访问另一个进程的资源。

进程间通信的目的:

1.数据传输

2.通知事件

3.进程控制

4.资源共享

进程间通信的方式

进程间通信的方式.png

匿名管道

匿名管道也叫无名管道,它是UNIX系统最古老的IPC(进程间通信)的方式

所有的UNIX系统都支持这种通信机制

管道的特点:

管道的特点.png


父子进程通过匿名管道通信

创建匿名管道

#include<unistd.h>int pipe(int filedes[2]);

返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道

注意:匿名管道只能用于具有关系进程之间的通信

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include<cstdio>
using namespace std;
int main()
{int pipefd[2];int ret = pipe(pipefd);if (ret == -1){perror("pipe");return -1;}pid_t pid = fork();if (pid > 0){char buff[1024]{0};while(1){printf("开始读数据............\n");int len = read(pipefd[0], buff, sizeof(buff)); // read 默认阻塞sleep(1);printf("prcv = %s ,pid = %d \n", buff, getpid());}}else if (pid == 0){while(1){printf("开始写数据中.........\n");const char *str = "hello world";write(pipefd[1], str, strlen(str));sleep(1);}}
}

查看缓冲区大小命令

ulimit -a

查看缓冲区大小函数

与pathconf函数功能一样,只是第一个参数不一样,pathconf的第一个参数 pathname是路径名,数据类型是字符数组指针,而fpathconf的第一个参数 files是一个已打开文件的文件标识符,数据类型是一个整数。两个函数的第二个参数name完全一样。

#include <unistd.h>long fpathconf(int fd, int name);
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include<cstdio>
using namespace std;
int main()
{int pipefd[2];int ret = pipe(pipefd);long size = fpathconf(pipefd[0],_PC_PIPE_BUF);printf("pipe size = %d\n",size);
}

匿名管道通信案例

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include<cstdio>
#include<cstdlib>
using namespace std;
int main()
{// 创建一个管道int fd[2];int ret = pipe(fd);if(ret == -1) {perror("pipe");exit(0);}// 创建子进程pid_t pid = fork();if(pid > 0) {// 父进程// 关闭写端close(fd[1]);// 从管道中读取char buf[1024] = {0};int len = -1;while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {// 过滤数据输出printf("%s", buf);memset(buf, 0, 1024);}wait(NULL);} else if(pid == 0) {// 子进程// 关闭读端close(fd[0]);// 文件描述符的重定向 stdout_fileno -> fd[1]dup2(fd[1], STDOUT_FILENO);// 执行 ps auxexeclp("ps", "ps", "aux", NULL);perror("execlp");exit(0);} else {perror("fork");exit(0);}}

管道的读写特点和管道设置为非阻塞

管道的读写特点: 使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作) 1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端 读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。

2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程

也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后, 再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。

3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程 向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。

4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程 也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞, 直到管道中有空位置才能再次写入数据并返回。总结: 读管道: 管道中有数据,read返回实际读到的字节数。 管道中无数据: 写端被全部关闭,read返回0(相当于读到文件的末尾) 写端没有完全关闭,read阻塞等待 写管道: 管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号) 管道读端没有全部关闭: 管道已满,write阻塞 管道没有满,write将数据写入,并返回实际写入的字节数

案例

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
/*设置管道非阻塞int flags = fcntl(fd[0], F_GETFL);  // 获取原来的flagflags |= O_NONBLOCK;            // 修改flag的值fcntl(fd[0], F_SETFL, flags);   // 设置新的flag
*/
int main() {// 在fork之前创建管道int pipefd[2];int ret = pipe(pipefd);if(ret == -1) {perror("pipe");exit(0);}// 创建子进程pid_t pid = fork();if(pid > 0) {// 父进程printf("i am parent process, pid : %d\n", getpid());// 关闭写端close(pipefd[1]);// 从管道的读取端读取数据char buf[1024] = {0};int flags = fcntl(pipefd[0], F_GETFL);  // 获取原来的flagflags |= O_NONBLOCK;            // 修改flag的值fcntl(pipefd[0], F_SETFL, flags);   // 设置新的flagwhile(1) {int len = read(pipefd[0], buf, sizeof(buf));printf("len : %d\n", len);printf("parent recv : %s, pid : %d\n", buf, getpid());memset(buf, 0, 1024);sleep(1);}} else if(pid == 0){// 子进程printf("i am child process, pid : %d\n", getpid());// 关闭读端close(pipefd[0]);char buf[1024] = {0};while(1) {// 向管道中写入数据char * str = "hello,i am child";write(pipefd[1], str, strlen(str));sleep(5);}}return 0;
}

有名管道

创建管道的方式

通过命令创建有名管道

mkfifo name

通过函数创建有名管道

#include <sys/types.h>
#include <sys/stat.h>int  mknod( const char  * pathname ,  mode_t  mode ,  dev_t  dev);int  mkfifo( const char  * pathname ,  mode_t  mode);

一旦使用mkfifo 创建了一个FIFO,就可以使用open函数打开了,常见的文件IO函数都可以使用。

使用命令创建管道

mkfifo tt

命令创建管道.png

使用函数创建管道

int main()
{int ret = mkfifo("test1",0664);if(ret == -1){perror("mkfifo");exit(0);}return 0;
}

有名管道的创建.png

进程管道读写测试

write.cpp

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>// 向管道中写数据
/*有名管道的注意事项:1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道读管道:管道中有数据,read返回实际读到的字节数管道中无数据:管道写端被全部关闭,read返回0,(相当于读到文件末尾)写端没有全部被关闭,read阻塞等待写管道:管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)管道读端没有全部关闭:管道已经满了,write会阻塞管道没有满,write将数据写入,并返回实际写入的字节数。
*/
int main()
{// 1.判断文件是否存在int ret = access("test", F_OK);if (ret == -1){printf("管道不存在,创建管道\n");// 2.创建管道文件ret = mkfifo("test", 0664);if (ret == -1){perror("mkfifo");exit(0);}}// 3.以只写的方式打开管道int fd = open("test", O_WRONLY);if (fd == -1){perror("open");exit(0);}// 写数据for (int i = 0; i < 100; i++){char buf[1024];sprintf(buf, "hello, %d\n", i);printf("write data : %s\n", buf);write(fd, buf, strlen(buf));sleep(1);}close(fd);return 0;
}

read.cpp

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>// 从管道中读取数据
int main() {// 1.打开管道文件int fd = open("test", O_RDONLY);if(fd == -1) {perror("open");exit(0);}// 读数据while(1) {char buf[1024] = {0};int len = read(fd, buf, sizeof(buf));if(len == 0) {printf("写端断开连接了...\n");break;}printf("recv buf : %s\n", buf);}close(fd);return 0;
}

内存映射

内存映射是将磁盘中的数据映射到内存当中,用户通过修改内存就能修改磁盘文件

20130612185647406.jpg

函数原型

#include<sys/mman.h>
#include <sys/types.h>//这里提供类型pid_t和size_t的定义
#include <sys/stat.h>
#include <fcntl.h>
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
1、pathname:

在open函数中第一个参数pathname是指向想要打开的文件路径名,或者文件名。我们需要注意的是,这个路径名是绝对路径名。文件名则是在当前路径下的。

2、flags:

flags参数表示打开文件所采用的操作,我们需要注意的是:必须指定以下三个常量的一种,且只允许指定一个

  • O_RDONLY:只读模式

  • O_WRONLY:只写模式

  • O_RDWR:可读可写

以下的常量是选用的,这些选项是用来和上面的必选项进行按位或起来作为flags参数。

  • O_APPEND 表示追加,如果原来文件里面有内容,则这次写入会写在文件的最末尾。

  • O_CREAT 表示如果指定文件不存在,则创建这个文件

  • O_EXCL 表示如果要创建的文件已存在,则出错,同时返回 -1,并且修改 errno 的值。

  • O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。

  • O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。

  • O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)

以下三个常量同样是选用的,它们用于同步输入输出

  • O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。

  • O_RSYNC read 等待所有写入同一区域的写操作完成后再进行

  • O_SYNC 等待物理 I/O 结束后再 write,包括更新文件属性的 I/O

3、mode:

mode参数表示设置文件访问权限的初始值,和用户掩码umask有关,比如0644表示-rw-r–r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。要注意的是,有以下几点

  • 文件权限由open的mode参数和当前进程的umask掩码共同决定。

  • 第三个参数是在第二个参数中有O_CREAT时才作用,如果没有,则第三个参数可以忽略

使用内存映射实现进程间通信
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <cstdio>
#include<sys/mman.h>
#include<fcntl.h> 
#include <sys/types.h>
#include <sys/stat.h>using namespace std;
/*
使用内存映射实现进程间通信*/
int main()
{int fd = open("test.txt", O_RDWR);int size = lseek(fd, 0, SEEK_END);// 创建内存映射区void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) {perror("mmap");exit(0);}pid_t pid = fork();if (pid > 0) {strcpy((char*)ptr, "hello world");// 父进程}else {// 子进程char buf[64]{ 0 };strcpy(buf, (char*)ptr);printf("read data = %s", buf);}// 关闭内存映射区munmap(ptr, size);}

输出

read data = hello world

内存映射的注意事项

1.如果对mmap的返回值做++操作,munmap是否能成功?

 ```c++void * ptr = mmap(...)ptr++ // 可以++munmap(ptr,len)  // error 要保存首地址```
  1. 如果open时O_RDONLY,mmap时prot参数指定PROT_READ|PROT_WRITE会怎么样?

    错误,要返回MAP_FAILED

open函数中的权限建议和prot中的权限保持一致

3.如果文件偏移量为1000会怎么样?

偏移量必须是1024的整数倍,返回MAP_FAILED

4.mmap什么情况下会调用失败?

第二个参数 - lengh = 0第三个参数 port- 只指定了写权限- PROT_READ | PORT|WRITE第五个参数 fd 通过open打开文件时指定的参数使用 O_RDONLY / O_WRONLY

5.可以open的时候O_CREAT一个新文件来创建映射区吗?

可以,但是创建文件的大小不能为0

可以对新文件进行扩展

-lessk()

- truncate()

6.mmap后关闭文件描述符,对mmap映射有没有影响?

映射区还存在,创建映射区的fd被关闭,没有任何影响

7.对映射区越界操作 会怎样?

越界操作访问的是非法的内存,会导致段错误 (Segment fault)


使用内存映射实现文件拷贝
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <cstdio>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
using namespace std;
int main()
{int fd = open("test.txt", O_RDWR);if (fd == -1){perror("open");exit(0);}// 拓展文件大小int len = lseek(fd, 0, SEEK_END);int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664);if (fd1 == -1){perror("open");exit(0);}// 拓展新文件truncate("cpy.txt", len);write(fd1, " ", 1);// 分别做内存映射void *x = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);void *x1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);if (x == MAP_FAILED){perror("mmap");exit(0);}// 内存拷贝memcpy(x, x1, len);// 释放资源munmap(x, len);munmap(x1, len);close(fd1);close(fd);
}

匿名映射

匿名映射:不需要文件实体进行内存映射

代码实现

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <cstdio>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <wait.h>
using namespace std;
int main()
{int len = 4096;void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (ptr == MAP_FAILED){perror("mmap");exit(-1);}// 父子进程间通信pid_t pid = fork();if (pid > 0){strcpy((char *)ptr, "hello world\n");wait(NULL);}else if (pid == 0){sleep(1);printf("%s \n", (char *)ptr);}int ret = munmap(ptr, len);if (ret == -1){perror("munmap");exit(-1);}
}

信号概述

信号是Linux进程间通信最古老的方式之一,是事件发生时对进程的通知机制,有时也称为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。

使用信号的两个目的:

  • 让进程知道已经发生了一个特定的事件

  • 强迫进程执行它自己代码中的信号处理程序

信号的特点:

  • 简单

  • 不能携带大量信息

  • 满足某个特定条件才发送

  • 优先级比较高

查看系统定义的信号列表

kill -l

前三十一个为常规信号,其余为实时信号。

信号的5种默认处理动作

信号处理.png

kill raise abort 函数

函数原型

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

函数参数: pid—进程号或者某个进程组的编号。

  • 如果该值是正数,代表信号发给某个进程;

  • 如果该值是0,代表信号发给调用该函数的进程所在组的其他所有进程。

  • 如果该值是-1,信号发送给该进程允许发送的所有进程,除了进程1(init)。

  • 如果该值小于-1,该信号发送给进程组ID为-pid内的所有进程。sig—信号的编号或者宏值。 如果信号的值为0,

  • 不会发送任何信号,可以用来检查该进程ID或进程组ID是否存在。

返回值:

如果至少一个信号被发送成功,返回0;如果错误返回-1,并且全局变量errno被设置成相应的值。

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <cstdio>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <wait.h>
using namespace std;
int main()
{pid_t pid = fork();if (pid == 0){for (int i = 0; i < 5; i++)cout << "chird process\n", sleep(1);}else if (pid > 0){cout << "parent process\n";sleep(2);cout << "kill child process now \n";kill(pid, SIGINT);}
}


int raise(int sig) // 参数  : 要发送的信号

功能:向进程发送信号(给调用者发送一个信号,等价于kill(getpid(), sig)

返回值:默认返回0,发送失败返回 -1


void abort(void )// 

函数说明:发送SIGABRT信号(值为6)给当前进程,杀死当前进程,等价于kill(getpid(), SIGABRT)

alarm 函数

1.引用头文件:

#include <unistd.h>;

2.函数标准式:

unsigned int alarm(unsigned int seconds); // 参数 :  倒计时的时长(单位:秒) 如果倒计时为0,不进行倒计时

作用 :

设置定时器,函数调用,开始倒计时,当倒计时为0的时候会给进程发送一个SIGALARM信号

SIGALARM:

返回值:

倒计时剩余的时间

案例 1
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <cstdio>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <wait.h>
using namespace std;
int main()
{int seconds = alarm(5);printf("seconds = %d \n",seconds); // 0sleep(2);seconds = alarm(2); // 不阻塞printf("seconds = %d \n",seconds);while(1){}}

输出

alarm.png

案例 2 : 计算机一秒钟可以数多少个数
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <cstdio>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <wait.h>
using namespace std;
int main()
{alarm(1);int i = 0;while(1){printf("%i\n",i++);}}

./alarm >> a.txt

输出

663565

总结:

实际的时间= 内核时间+ 用户时间+消耗的时间

进行文件IO操作的时候比较消耗时间

定时器与进程的状态无关 (自然定时器)


setitimer 定时器函数

头文件

#include<sys/time.h>

函数原型

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

which有以下可选参数类型:

ITIMER_REAL:以系统真实的时间来计算,它送出SIGALRM信号。

ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。

ITIMER_PROF:以该进程在用户态下和内核态下所费的时间来计算。它送出SIGPROF信号。

功能: 实现周期性的定时

返回值:

成功返回0,失败返回 -1

过3秒以后,每隔2秒钟定时一次

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>// 过3秒以后,每隔2秒钟定时一次
int main()
{struct itimerval new_value;// 设置间隔的时间new_value.it_interval.tv_sec = 2;new_value.it_interval.tv_usec = 0;// 设置延迟的时间,3秒之后开始第一次定时new_value.it_value.tv_sec = 3;new_value.it_value.tv_usec = 0;int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的printf("定时器开始了...\n");if (ret == -1){perror("setitimer");exit(0);}getchar();return 0;
}	

signal 信号捕捉 函数

函数原型

void (*signal(int sig, void (*func)(int)))(int)

功能

设置某个信号的捕捉行为

返回值

该函数返回信号处理程序之前的值,当发生错误时返回 SIG_ERR

实例

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
using namespace std;
void sighandler(int);int main()
{// 注册信号捕捉signal(SIGINT, sighandler);while (1){printf("开始休眠一秒钟...\n");sleep(1);}return 0;
}void sighandler(int signum)
{printf("捕获信号 %d,跳出...\n", signum);exit(1);
}

信号集函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统 实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做 任何解释,比如用printf直接打印sigset_t变量是没有意义的

/*int sigemptyset(sigset_t *set);- 功能:清空信号集中的数据,将信号集中的所有的标志位置为0- 参数:set,传出参数,需要操作的信号集- 返回值:成功返回0, 失败返回-1int sigfillset(sigset_t *set);- 功能:将信号集中的所有的标志位置为1- 参数:set,传出参数,需要操作的信号集- 返回值:成功返回0, 失败返回-1int sigaddset(sigset_t *set, int signum);- 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号- 参数:- set:传出参数,需要操作的信号集- signum:需要设置阻塞的那个信号- 返回值:成功返回0, 失败返回-1int sigdelset(sigset_t *set, int signum);- 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号- 参数:- set:传出参数,需要操作的信号集- signum:需要设置不阻塞的那个信号- 返回值:成功返回0, 失败返回-1int sigismember(const sigset_t *set, int signum);- 功能:判断某个信号是否阻塞- 参数:- set:需要操作的信号集- signum:需要判断的那个信号- 返回值:1 : signum被阻塞0 : signum不阻塞-1 : 失败*/

阻塞信号集和未决信号集

1.用户通过键盘 Ctrl + C, 产生2号信号SIGINT (信号被创建)

2.信号产生但是没有被处理 (未决) - 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集) - SIGINT信号状态被存储在第二个标志位上 - 这个标志位的值为0, 说明信号不是未决状态 - 这个标志位的值为1, 说明信号处于未决状态

3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较 - 阻塞信号集默认不阻塞任何的信号 - 如果想要阻塞某些信号需要用户调用系统的API

4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了 - 如果没有阻塞,这个信号就被处理 - 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理

      #### 案例
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
using namespace std;int main()
{// 创建信号集sigset_t set;// 清空信号集的内容sigemptyset(&set);// 判断信号 SIGINT 是否在信号集里面int ret = sigismember(&set, SIGINT);if (ret == 0){printf("SIGINT 不阻塞\n");}else{printf("SIGINT 阻塞\n");}// 添加几个信号sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 判断SIGINT 是否在信号集当中ret = sigismember(&set, SIGINT);if (ret == 0){printf("SIGINT 不阻塞\n");}else{printf("SIGINT 阻塞\n");}ret = sigismember(&set, SIGQUIT);if (ret == 0){printf("SIGQUIT 不阻塞\n");}else{printf("SIGQUIT 阻塞\n");}// 删除一个信号,判断SIGQUIT 是否在当前信号集当中sigdelset(&set, SIGQUIT);ret = sigismember(&set, SIGQUIT);if (ret == 0){printf("SIGQUIT 不阻塞\n");}else{printf("SIGQUIT 阻塞\n");}
}

sigprocmask函数
#include <signal.h>
int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );

功能:将自定义信号集中的数据设置到内核中(阻塞,非阻塞)

// 编写一个程序,把所有的常规信号(1-31)的未决状态打印到屏幕
// 设置某些信号是阻塞的,通过键盘产生这些信号#include <iostream>
#include <cstring>
#include <unistd.h>
#include <cstdio>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include<signal.h>
using namespace std;
int main() {// 设置2、3号信号阻塞sigset_t set;sigemptyset(&set);// 将2号和3号信号添加到信号集中sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);// 修改内核中的阻塞信号集sigprocmask(SIG_BLOCK, &set, NULL);int num = 0;while(1) {num++;// 获取当前的未决信号集的数据sigset_t pendingset;sigemptyset(&pendingset);sigpending(&pendingset);// 遍历前32位for(int i = 1; i <= 31; i++) {if(sigismember(&pendingset, i) == 1) {printf("1");}else if(sigismember(&pendingset, i) == 0) {printf("0");}else {perror("sigismember");exit(0);}}printf("\n");sleep(1);if(num == 10) {// 解除阻塞sigprocmask(SIG_UNBLOCK, &set, NULL);}}return 0;
}

sigaction 信号捕捉函数

何为信号:信号就是由用户、系统或进程发送给目标进程的信息,以通知目标进程中某个状态的改变或是异常。

信号产生:总体来说,其产生的条件有两种,分别是:硬件和软件原因,又称为:硬中断和软中断。可细分为如下几种原因:

①系统终端Terminal中输入特殊的字符来产生一个信号,比如按下:ctrl+\会产生SIGQUIT信号。

②系统异常。比如访问非法内存和浮点数异常。

③系统状态变化。如设置了alarm定时器,当该定时器到期时候会引起SIGVTALRM信号。

④调用了kill命令或是kill函数。

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

参数:

  • 参数1:要捕获的信号

  • 参数2:接收到信号之后对信号进行处理的结构体

  • 参数3:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来做备份)。如果不需要备份,此处可以填NULL

返回值:成功返回0,失败返回-1

内核实现信号捕捉的流程

信号捕捉的流程.png

测试代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>/*自定义的信号捕捉函数*/
void sig_int(int signo)
{printf("catch signal SIGINT\n");//单次打印sleep(10);printf("----slept 10 s\n");
}int main(void)
{struct sigaction act;		act.sa_handler = sig_int;act.sa_flags = 0;sigemptyset(&act.sa_mask);		//不屏蔽任何信号sigaddset(&act.sa_mask, SIGQUIT);sigaction(SIGINT, &act, NULL);printf("------------main slept 10\n");sleep(10);while(1);//该循环只是为了保证有足够的时间来测试函数特性return 0;
}


SIGCHLD 信号

SIGCHLD 信号产生的条件

  • 子进程终止时

  • 子进程接收到SIGSTOP信号停止时

  • 子进程处在停止态,接受到SIGCONT后唤醒时

以上三种条件都会给父进程发送SIGCHLD信号,父进程默认忽略

SIGCHLD解决僵尸进程
#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
void myFun(int num)
{printf("捕捉到的信号 :%d\n", num);// 回收子进程PCB的资源// while(1) {//     wait(NULL);// }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;}}
}int main()
{// 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉sigset_t set;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigprocmask(SIG_BLOCK, &set, NULL);// 创建一些子进程pid_t pid;for (int i = 0; i < 20; i++){pid = fork();if (pid == 0){break;}}if (pid > 0){// 父进程// 捕捉子进程死亡时发送的SIGCHLD信号struct sigaction act;act.sa_flags = 0;act.sa_handler = myFun;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());}return 0;
}

共享内存

什么是共享内存?

共享内存.png

内存映射是共享内存的一种方式。共享内存是一种允许不同进程或线程访问相同物理内存区域的技术,而内存映射是实现这种共享的具体方法之一。

内存映射通常是通过将一个文件或其他资源映射到一个进程的地址空间实现的。这样,当多个进程映射同一个文件或资源时,它们实际上在访问相同的内存区域,从而实现了内存共享。内存映射可以用于进程间通信,以及实现文件的高效访问。

总的来说,共享内存是一种概念,而内存映射是实现共享内存的一种具体技术。

共享内存相关操作

(1)创建/打开共享内存:创建共享内存需要用到shmget()函数,原型如下

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flag);

创建成功返回共享内存的ID,出错返回-1。

参数key为共享内存的键值,参数size为创建共享内存的大小,参数flag为调用函数的操作类型。参数key和参数flag共同决定的shmget()的作用:

  • key为IPC_PRIVATE时,创建一个新的共享内存,flag取值无效。

  • key不为IPC_PRIVATE,且flag设置了IPC_CREAT位,而没有设置IPC_EXCL位时,如果key为内核中的已存在的共享内存键值,则打开,否则创建一个新的共享内存。

  • key不为IPC_PRIVATE,且flag设置了IPC_CREAT和IPC_EXCL位时,则只执行创建共享内存操作。如果key为内核中的已存在的共享内存键值,返回EEXIST错误。

(2)共享内存的附加(映射)

创建一个共享内存后,某个进程若想使用,需要将此内存区域附加(attach)到自己的进程空间(或称地址映射),需要用到shmat()函数:

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int *shmat(int shmid, const void *addr, int flag);

运行成功返回指向共享内存段的地址指针,出错返回-1。

参数shmid为共享内存的ID,参数addr和参数flag共同说明要引入的地址值,通常只有2种用法:

  • addr为0,表明让内核来决定第1个可引用的位置

  • addr非0,且flag中指定SHM_RND,则此段引入到addr所指向的位置。

shmat()函数执行成功后,会将shmid的共享内存段的shmid_ds结构的shm_nattch计数器值加1

(3)共享内存的分离 当进程使用完共享内存后,需要将共享内存从其进程空间中去除(detach),使用shmdt()函数:

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *addr);

运行成功返回0,出错返回-1。

参数addr是调用shmat()函数的返回值,即共享内存段的地址指针。shmdt()函数执行成功后,shm_nattch计数器值减1。

(4)共享内存的控制 使用shmctl()可以对共享内存段进行多种控制操作,函数原型:

#include <sys/types,h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_s *buf);

运行成功返回0,出错返回-1。

参数shmid为共享内存的ID,参数cmd指明了所要进行的操作,与参数*buf配合使用:

取shmid指向的共享内存的shmid_ds结构,对参数buf指向的结构赋值

案例

  • 生成key,ftok()

  • 使用key创建/获得一个共享内存,shmget()

  • 映射共享内存,得到虚拟地址,shmat()

  • 使用共享内存,通过地址指针

  • 移除映射,shmdt()

  • 销毁共享内存,shmctl()

#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <iostream>
#include <sys/wait.h>
using namespace std;
int main()
{// generate keykey_t key = ftok("./", 200);printf("key=%#x\n", key);// create a share memoryint shmid = shmget(key, 8, IPC_CREAT | 0666 | IPC_EXCL);if (shmid == -1){perror("shmget failed\n");exit(1);}printf("shmid=%#x\n", shmid);// map share memory to get the virtual addressvoid *p = shmat(shmid, 0, 0);if ((void *)-1 == p){perror("shmat failed");exit(2);}// write data to share memoryint *pi = (int *)p;*pi = 0xaaaaaaaa;*(pi + 1) = 0x55555555;// remove the mapif (shmdt(p) == -1){perror("shmdt failed");exit(3);}// delete the share memoryprintf("use Enter to destroy the share memory\n");getchar();if (shmctl(shmid, IPC_RMID, NULL) == -1){perror("shmctl");exit(4);}return 0;
}

共享内存操作命令

共享内存操作命令.png


守护进程

终端.png

进程组

进程组和会话之间形成了一种两级层次关系,进程组是一组进程相关的集合。

进行组是由一个或多个共享同一进程组标识符(PGID)的进程组成

进程组拥有一个生命周期,其开始时间为首进程创建组的时间,结束时间为最后一个进程退出的时间。一个进程可能会因为进程结束而退出进程组,也有可能会因为别的进程组加入而退出当前进程组。


会话

会话是一组进程组的集合。会话首进程是创建新会话的进程,其进程id会成为会话id。

一个会话中的所有进程共享单个控制终端。

在任意时刻,会话中的其中一个进程组会成为终端的前台进程组,其他进程组会成为后台进程组。

当控制终端的连接建立起来之后,会话首进程会成为终端的控制进程。


守护进程

守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周 期性地执行某种任务或等待处理某些发生的事件。一般采用以 d 结尾的名字。

  • 守护进程具备下列特征: 生命周期很长,守护进程会在系统启动的时候被创建并一直运行直至系统被关闭。

  • 它在后台运行并且不拥有控制终端。没有控制终端确保了内核永远不会为守护进 程自动生成任何控制信号以及终端相关的信号(如 SIGINT、SIGQUIT)。

  • Linux 的大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd, Web 服务器 httpd 等

守护进程的创建步骤

  • 执行一个 fork(),之后父进程退出,子进程继续执行。

  • 子进程调用 setsid() 开启一个新会话。

  • 清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限。

  • 修改进程的当前工作目录,通常会改为根目录(/)。

  • 关闭守护进程从其父进程继承而来的所有打开着的文件描述符。 ◼

  • 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2() 使所有这些描述符指向这个设备。

  • 核心业务逻辑

案例

/*写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中。
*/#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>void work(int num)
{// 捕捉到信号之后,获取系统时间,写入磁盘文件time_t tm = time(NULL);struct tm *loc = localtime(&tm);// char buf[1024];// sprintf(buf, "%d-%d-%d %d:%d:%d\n",loc->tm_year,loc->tm_mon// ,loc->tm_mday, loc->tm_hour, loc->tm_min, loc->tm_sec);// printf("%s\n", buf);char *str = asctime(loc);int fd = open("time.txt", O_RDWR | O_CREAT | O_APPEND, 0664);write(fd, str, strlen(str));close(fd);
}int main()
{// 1.创建子进程,退出父进程pid_t pid = fork();if (pid > 0){exit(0);}// 2.将子进程重新创建一个会话setsid();// 3.设置掩码umask(022);// 4.更改工作目录chdir("/home/nowcoder/");// 5. 关闭、重定向文件描述符int fd = open("/dev/null", O_RDWR);dup2(fd, STDIN_FILENO);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);// 6.业务逻辑// 捕捉定时信号struct sigaction act;act.sa_flags = 0;act.sa_handler = work;sigemptyset(&act.sa_mask);sigaction(SIGALRM, &act, NULL);struct itimerval val;val.it_value.tv_sec = 2;val.it_value.tv_usec = 0;val.it_interval.tv_sec = 2;val.it_interval.tv_usec = 0;// 创建定时器setitimer(ITIMER_REAL, &val, NULL);// 不让进程结束while (1){sleep(10);}return 0;
}

相关文章:

一文让你玩转Linux多进程开发

Linux多进程开发 主要介绍多进程开发时的要点 进程状态转换 进程反应了进程执行的变化。 进程的状态分为三种 ,运行态,阻塞态,就绪态 在五态模型中分为以下几种,新建态&#xff0c;就绪态&#xff0c;运行态&#xff0c;阻塞态,终止态。 运行态&#xff1a;进程占用处理器正在运…...

Linux线程同步实例

线程同步实例 1. 生产消费者模型基本概念2. 基于BlockingQueue的生产者消费者模型3. 基于环形队列的生产消费模型4. 线程池 1. 生产消费者模型基本概念 生产者消费者模型是一种常用的并发设计模式&#xff0c;它可以解决生产者和消费者之间的速度不匹配、解耦、异步等问题。生…...

LuatOS-SOC接口文档(air780E)-- iconv - iconv操作

iconv.open(tocode, fromcode)# 打开相应字符编码转换函数 参数 传入值类型 解释 string 释义&#xff1a;目标编码格式 取值&#xff1a;gb2312/ucs2/ucs2be/utf8 string 释义&#xff1a;源编码格式 取值&#xff1a;gb2312/ucs2/ucs2be/utf8 返回值 返回值类型 解…...

matlab第三方硬件支持包下载和安装

1、在使用matlab内部的附加功能安装时&#xff0c;由于matlab会验证是否正版无法打开 2、在matlab官网直接找到对应的硬件支持包下载&#xff0c;但是是下图的安装程序 可以直接在matlab中跳转到该程序所在的文件夹双击安装&#xff0c;但是安装到最后出错了 3.根据出错时mala…...

docker compose和consul(服务注册与发现)

一、Docker-compose 简介 Docker-Compose项目是基于Python开发的Docker官方开源项目&#xff0c;负责实现对Docker容器集群的快速编排。 Docker-Compose将所管理的容器分为三层&#xff0c;分别是 工程&#xff08;project&#xff09;&#xff0c;服务&#xff08;service&a…...

使用Python进行钻石价格分析

钻石是最昂贵的宝石之一。钻石的质量通常以其重量&#xff08;克拉&#xff09;、净度、颜色和切工来评估。重量越大、净度越高、色彩纯净、切工精细的钻石价格也越高。其中&#xff0c;4C标准是衡量钻石质量的国际标准&#xff0c;即克拉&#xff08;Carat&#xff09;、净度&…...

Java日期查询

本实例使用有关日期处理和日期格式化的类实现一个日期查询的功能&#xff0c;即查询指定日期所在周的周一日期、两个指定日期间相差的天数和指定日期为所在周的星期几的日期 3 个功能。 从功能上来看&#xff0c;本实例至少需要定义 3 个方法&#xff0c;分别完成&#xff1a;获…...

uniapp 运行到 app 报错 Cannot read property ‘nodeName‘ of null

uniapp 运行到某一个页面&#xff0c;报错&#xff0c;h5没有问题 Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://new-issue.vuejs.org/?repovuejs/coreat <GuiPagecustomHeadertruecustomF…...

Mac M1通过homebrew安装Redis报错(perl: unknown or unsupported macOS version: :dunno)

〇、解决方案 升级homebrew&#xff0c;命令如下&#xff1a; brew update-reset一、问题现象 通过命令brew install redis安装Redis&#xff0c;异常如下&#xff1a; fatal: not in a git directory Warning: No remote origin in /opt/homebrew/Library/Taps/homebrew/h…...

如何在 Spring Boot 中进行分布式追踪

在 Spring Boot 中进行分布式追踪 分布式系统中的应用程序由多个微服务组成&#xff0c;它们可以位于不同的服务器、容器或云中。当出现问题时&#xff0c;如性能瓶颈、错误或延迟&#xff0c;了解问题的根本原因变得至关重要。分布式追踪是一种用于跟踪和分析分布式应用程序性…...

Lniux三剑客——Grep

前言 echo guangge{01…100…2} 第二个是间隔多少个计数 命令别名 alias&#xff0c; unalias &#xff0c; 作用是封装命令&#xff1a; alias rm ‘rm -i’ 命令历史 history !行号 !! 上一次的命令 ctrl a 移动到行首 ctrl e 移动到行尾 Grep 格式&#xff1a; gre…...

选实验室超声波清洗机易忽视的内容?小型清洗机的优点有?

实验室超声波清洗机如今在行业内占据着重要的一席之地&#xff0c;摒弃了传统模式&#xff0c;坚持以超声波为主的清洗方式&#xff0c;在市场中获得的反响强烈。服务好&#xff0c;有诚信的实验室超声波清洗机能够消除客户的后顾之忧&#xff0c;工作人员会以真诚态度向客户提…...

基于Java使用SpringBoot+Vue框架实现的前后端分离的美食分享平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 在当今社会&#xff0…...

开源数据库MySQL 8.0 OCP认证精讲视频、环境和题库 之二

修改用户的初始密码&#xff1a; mysql>alteruserrootlocalhostidentifiedbyQaz1234&#xff1b; 或者&#xff1a; mysql>alteruseruser0identifiedbyQaz_1234; 在版本5.x中&#xff1a; mysql>setpasswordpassword(Qaz_1234); 可执行文件&#xff1a; 服务器端&…...

AI对网络安全的影响与挑战

近年来&#xff0c;随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;网络安全领域也开始逐渐引入生成式AI应用。根据最新的数据研究&#xff0c;生成式AI对网络安全和合规的影响最大&#xff0c;同时也包括了IT和云的运维、硬件和软件支持领域。通过AI和自动…...

微信小程序备案流程操作详解,值得收藏

目录 一、小程序备案法律法规参考 二、备案前准备 2.1 备案入口 2.1.1、未上架小程序 2.1.2、已上架小程序 (二)备案类型 (三)备案材料准备 3.1、小程序备案材料 3.2、前置审批材料 3.3、个人备案 3.4、非个人备案 三、备案整体流程 (一)备案信息填写 1、主体信息…...

【NLTK系列01】:nltk库介绍

一、说明 NLTK是个啥&#xff1f;它是个复杂的应用库&#xff0c;可以实现基本预料库操作&#xff0c;比如&#xff0c;、将文章分词成独立token&#xff0c;等操作。从词统计、标记化、词干提取、词性标记&#xff0c;停用词收集&#xff0c;包括语义索引和依赖关系解析等。 …...

人机环境系统智能有利于防止人工智能失控

当前&#xff0c;人工智能的失控是一个备受关注的话题。尽管目前还没有出现完全失控的人工智能系统&#xff0c;但确实存在一些潜在的风险和挑战需要我们重视和应对。一些可能导致人工智能失控的因素包括&#xff1a; 误用和恶意使用&#xff1a;人工智能技术可以被用于恶意活动…...

用于多目标检测的自监督学习(SELF-SUPER VISED LEARNING FOR MULTIPLE OBJECTDETECTION)

在本章中,我们提出了一种新的自监督学习(SSL)技术,以从头顶图像中提供关于实例分割不确定性的模型信息。我们的SSL方法通过使用测试时数据增强和基于回归的旋转不变伪标签细化技术来改进对象检测。我们的伪标签生成方法提供多个经过几何变换的图像作为卷积神经网(CNN)的输…...

HDLbits: ps2data

这一题在上一题基础上多了一个输出&#xff0c;并且这个输出是不需要像上一题考虑出错的情况的&#xff0c;所以只要把输入in按次序排好就可以。我一开始的想法是在状态切换判断的always块里把in赋给out&#xff0c;但是不正确&#xff0c;代码如下&#xff1a; module top_mo…...

SpringCloudAlibaba SpringCloud SpringBoot 版本对照

由于 Spring Boot 3.0&#xff0c;Spring Boot 2.7~2.4 和 2.4 以下版本之间变化较大&#xff0c;目前企业级客户老项目相关 Spring Boot 版本仍停留在 Spring Boot 2.4 以下&#xff0c;为了同时满足存量用户和新用户不同需求&#xff0c;社区以 Spring Boot 3.0 和 2.4 分别为…...

Swift基础

本文是个比较简单的学习笔记&#xff0c;更详细的内容见 Swift官方文档 1、相等性比较 Swift标准库用 < 和 运算符定义了 >、>、<&#xff0c;所以实现 Comparable 的 < 运算符就会自动得到这些运算符的实现&#xff0c;实际上 Comparable 继承自 Equatable&…...

基于php+thinkphp+vue的校园二手交易网站

运行环境 开发语言&#xff1a;PHP 数据库:MYSQL数据库 应用服务:apache服务器 使用框架:ThinkPHPvue 开发工具:VScode/Dreamweaver/PhpStorm等均可 项目简介 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发…...

SystemVerilog Assertions应用指南 第一章(1.25章节 “first_match”运算符)

任何时候使用了逻辑运算符(如“and”和“or”)的序列中指定了时间窗,就有可能出现同一个检验具有多个匹配的情况。“ first match”构造可以确保只用第一次序列匹配,而丢弃其他的匹配。当多个序列被组合在一起,其中只需时间窗内的第一次匹配来检验属性剩余的部分时,“ first ma…...

python和go执行字符串表达式

1、python/eval python里可以使用内置的eval函数&#xff0c;来执行一个字符串表达式的结果&#xff0c;字符串表达式里可以是变量、函数、运算符等 def test():return True flag False print(eval("test() and True and flag" )) 执行结果为False 2、golang/go…...

Python算法练习 10.14

leetcode 2095 删除链表的中间节点 给你一个链表的头节点 head 。删除 链表的 中间节点 &#xff0c;并返回修改后的链表的头节点 head 。 长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点&#xff08;下标从 0 开始&#xff09;&#xff0c;其中 ⌊x⌋ 表示小于或等于…...

云上攻防-云原生篇Docker安全系统内核版本漏洞CDK自动利用容器逃逸

文章目录 云原生-Docker安全-容器逃逸&内核漏洞云原生-Docker安全-容器逃逸&版本漏洞-CVE-2019-5736 runC容器逃逸-CVE-2020-15257 containerd逃逸 云原生-Docker安全-容器逃逸&CDK自动化 云原生-Docker安全-容器逃逸&内核漏洞 细节部分在权限提升章节会详解&…...

C# Sqlite数据库的搭建及使用技巧

C# Sqlite数据库的搭建 前言: 今天我们来学一下Sqlite的数据库的搭建&#xff0c;Sqlite数据库不比MySqL数据库&#xff0c;SQlite数据是一个比较轻量级的数据库&#xff0c;SQLite提供了比较多的工具集&#xff0c;对数据基本上不挑&#xff0c;什么数据都可以处理&#xff…...

gerrit代码review使用基本方法

1、repo拉取代码 repo init -u ssh://gerrit.senseauto.com/senseauto_manifest -b develop -m senseauto-config.xml --repo-urlssh://gerrit.senseauto.com:29418/senseauto_repo --repo-branchdevelop --no-repo-verify repo sync -j4 repo forall -j 4 -p -c ‘git lfs p…...

网络监控与故障排除:netstat命令的使用指南

文章目录 概述什么是 netstat 命令&#xff1f;netstat 命令的作用和功能netstat 命令的常见用途 安装和基本用法安装 netstat 命令netstat 命令的基本语法查看活动网络连接 查看网络接口信息查看所有网络接口信息查看指定网络接口信息网络接口状态说明 网络连接状态显示所有连…...

Blender:渲染一个简单动画

接上 Blender&#xff1a;对模型着色_六月的翅膀的博客-CSDN博客 目标是做一个这种视频 先添加一个曲线&#xff0c;作为相机轨迹 然后添加一个相机 对相机添加物体约束&#xff0c;跟随路径&#xff0c;选择曲线&#xff0c;然后点击动画路径 假如对相机设置跟随路径后&…...

一篇文章带你用动态规划解决股票购买时机问题

动态规划的解题步骤可以分为以下五步&#xff0c;大家先好好记住 1.创建dp数组以及明确dp数组下标的含义 2.制定递推公式 3.初始化 4.遍历顺序 5.验证结果 股票购买时机问题的解题核心思路 当天的收益是根据前一天持有股票还是不持有股票的状态决定的 那么很自然的我们就想…...

【设计模式】使用建造者模式组装对象并加入自定义校验

文章目录 1.前言1.1.创建对象时的痛点 2.建造者模式2.1 被建造类准备2.2.建造者类实现2.3.构建对象测试2.4.使用lombok简化建造者2.5.lombok简化建造者的缺陷 3.总结 1.前言 在我刚入行不久的时候就听说过建造者模式这种设计模式&#xff0c;当时只知道是用来组装对象&#xf…...

简单聊聊低代码

在数字经济迅速发展的背景下&#xff0c;越来越多的企业开始建立健全业务系统、应用、借助数字化工具提升管理效率&#xff0c;驱动业务发展&#xff0c;促进业绩增长。在这一过程中&#xff0c;和许多新技术一样&#xff0c;低代码&#xff08;Low-code&#xff09;开发被推上…...

SystemVerilog Assertions应用指南 第一章(1.27章节 “within”运算符)

“ within”构造允许在一个序列中定义另一个序列。 seq1 within seq2 这表示seq1在seq2的开始到结束的范围内发生,且序列seq2的开始匹配点必须在seq1的开始匹配点之前发生,序列seq1的结束匹配点必须在seq2的结束匹配点之前结束。属性p32检查序列s32a在信号“ start”的上升沿和…...

2023年09月 C/C++(七级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 Python编程(1~6级)全部真题・点这里 第1题:红与黑 有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。 时间限…...

[Mono Depth/3DOD]单目3D检测基础

1. 数据增强 图像放缩和裁剪后&#xff0c;相机内参要做相应变化 import random def random_scale(image, calib, scale_range(0.8, 1.2)):scale random.uniform(*scale_range)width, height image.sizeimage image.resize((int(width * scale), int(height * scale)))cal…...

【Docker 内核详解】namespace 资源隔离(三):PID namespace

namespace 资源隔离&#xff08;三&#xff09;&#xff1a;PID namespace 1.PID namespace 中的 init 进程2.信号与 init 进程3.挂载 proc 文件系统4.unshare() 和 setns() PID namespace 隔离非常实用&#xff0c;它对进程 PID 重新标号&#xff0c;即两个不同 namespace 下的…...

1600*C. Game On Leaves(博弈游戏树)

Problem - 1363C - Codeforces 解析&#xff1a; 我们将目标结点 x 当作树的根&#xff0c;显然&#xff0c;到当 x 的度为 1 的时候&#xff0c;此时行动的人胜利。 我们假设现在的情况为&#xff0c;只剩余三个点&#xff0c;再选择任意一个点&#xff0c;则对方获胜。但是两…...

Apache Ant的安装

介绍 Apache Ant是一个Java库和一个 命令行工具&#xff0c;可以用来构建Java应用。Ant提供了许多内置的任务&#xff08;tasks&#xff09;&#xff0c;可以编译、组装、测试、运行Java应用。Ant也可以构建非Java应用&#xff0c;例如C、C应用。 Ant非常灵活&#xff0c;没有…...

考研:数学二例题--∞−∞和0⋅∞型极限

前言 本文只是例题&#xff0c;建议先参考具体如何做这类型例题。请到主文章中参考&#xff1a;https://blog.csdn.net/grd_java/article/details/132246630 ∞ − ∞ 和 0 ⋅ ∞ \infin - \infin 和 0\infin ∞−∞和0⋅∞ 例题 例1&#xff1a; lim ⁡ x → ∞ x 2 x 2 −…...

C++算法:图中的最短环

题目 现有一个含 n 个顶点的 双向 图&#xff0c;每个顶点按从 0 到 n - 1 标记。图中的边由二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi] 表示顶点 ui 和 vi 之间存在一条边。每对顶点最多通过一条边连接&#xff0c;并且不存在与自身相连的顶点。 返回图中 …...

C++学习——类其实也是一种作用域

以下内容源于C语言中文网的学习与整理&#xff0c;非原创&#xff0c;如有侵权请告知删除。 其实也是一种作用域&#xff0c;每个类都会定义它自己的作用域。在类的作用域之外&#xff0c;普通的成员只能通过对象&#xff08;可以是对象本身&#xff0c;也可以是对象指针或对象…...

Seata入门系列【4】undo_log、global_table、branch_table、lock_table字段及作用详解

1 客户端 1.1 undo_log 在AT模式中&#xff0c;需要在参与全局事务的数据库中&#xff0c;添加一个undo_log表&#xff0c;建表语句如下&#xff1a; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for undo_log -- --…...

虚幻引擎:数据表格的C++常用API

1.将数据表格中的所有数据存到一个数组中 //参数1 // 错误提示 //参数2 // 存储的数组 TArray<FKeyInfoHeader*> array; KeyInfoDT->GetAllRows<FKeyInfoHeader>(TEXT("错误"),array); 2.获取表格中所有的行名称 TArray<FName>array; …...

Java日期格式化(DateFormat类和SimpleDateFormat类)

格式化日期表示将日期/时间格式转换为预先定义的日期/时间格式。例如将日期“Fri May 18 15:46:24 CST2016” 格式转换为 “2016-5-18 15:46:24 星期五”的格式。 在 java 中&#xff0c;可以使用 DateFormat 类和 SimpleDateFormat 类来格式化日期&#xff0c;下面详细介绍这两…...

centos 7 lamp owncloud

OwnCloud是一款开源的云存储软件&#xff0c;基于PHP的自建网盘。基本上是私人使用&#xff0c;没有用户注册功能&#xff0c;但是有用户添加功能&#xff0c;你可以无限制地添加用户&#xff0c;OwnCloud支持多个平台&#xff08;windows&#xff0c;MAC&#xff0c;Android&a…...

屏幕亮度调节保护您的眼睛

官方下载地址&#xff1a; 安果移动 视频演示&#xff1a;屏幕亮度调节-保护您的眼睛_哔哩哔哩_bilibili 嗨&#xff0c;亲爱的用户&#xff0c;你是否有过这样的体验&#xff1a;夜晚安静的时刻&#xff0c;想要在抖音上看看热门的舞蹈、在快手上发现生活的 趣味、或是在哔…...

CentOS Linux下CMake二进制文件安装并使用Visual Studio调试

cmake安装——二进制安装(很简单&#xff0c;推荐&#xff01;&#xff01;) 1&#xff09;下载二进制包。首先就是官网下载二进制安装包(我们是64位系统&#xff0c;就下载对应的包)&#xff0c;这里。 例如&#xff1a;在/home/DOWNLOAD目录下执行&#xff0c;即下载二进制…...

ASP.net相关目录,相关配置文件和.后缀名解释

App_Data&#xff1a;用于存储应用程序的数据文件&#xff0c;例如数据库文件或其他本地文件。 App_Start&#xff1a;包含应用程序的启动配置文件&#xff0c;例如路由配置、日志配置等。 Content&#xff1a;存放应用程序的静态资源文件&#xff0c;如 CSS、JavaScript、图…...