最新资讯

  • 【Linux】进程间通信(二)

【Linux】进程间通信(二)

2025-04-27 21:37:29 0 阅读

目录

  • 三、system V共享内存
    • 3.1 共享内存的原理
    • 3.2 共享内存函数
      • 3.2.1 ftok函数
      • 3.2.2 shmget函数
      • 3.2.3 shmat函数
      • 3.2.4 shmdt函数
      • 3.2.5 shmctl函数
    • 3.3 共享内存的使用
      • 3.3.1 ftok函数与shmget函数的使用
      • 3.3.2 shmat函数的使用
      • 3.3.3 shmdt函数的使用
      • 3.3.4 shmctl函数的使用
        • 3.3.4.1 删除共享内存
        • 3.3.4.2 获取共享内存中的属性
      • 3.3.5 进行通信
    • 3.4 共享内存总结
  • 四、system V消息队列(略讲)
    • 4.1 消息队列的原理
    • 4.2 消息队列函数
      • 4.2.1 msgget 函数
      • 4.2.2 msgsnd函数
      • 4.2.3 msgrcv函数
      • 4.2.4 msgctl函数
  • 五、system V信号量
    • 5.1 预备知识
    • 5.2 信号量的原理
    • 5.2 信号量函数
      • 5.3.1 semget函数
      • 5.3.2 semctl函数
      • 5.3.3 semop函数
  • 六、内核是如何看待IPC资源的
  • 结尾

前面那篇文章讲述了进程间通信的目的、理解、发展及分类和管道相关的知识,本篇文章将讲述system V共享内存、消息队列、信号量以及内核是如何看待IPC资源的。进程间通信(一)


三、system V共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

3.1 共享内存的原理

进程间通信的前提是让不同的进程看到同一份资源,这份资源必须由操作系统提供。

一个进程想要进行进程间通信,首先先在物理内存中申请一段内存,然后在进程的进程地址空间中的共享区中找一段区间,然后通过页表将物理内存与进程地址空间映射起来,最后再将共享区中找到的区间的启示地址返回给上层,这时候这个进程就看到了一块系统级别的内存资源。然后第二个进程想跟第一个进程进行进程间通信,就在它自己的进程的进程地址空间中的共享区中找一段区间,然后通过页表将物理内存与进程地址空间映射起来。这样两个毫不相关的进程就看到了同一份资源。

那么如果我们想删除共享内存呢?首先我们就需要将进程中与共享内存相关的地址在页表中删除,那么共享区中与共享内存相关的地址就全部失效,这里也可以帮助我们理解一下之前的知识,我们知道使用malloc、new等申请空间的时候是在进程地址空间中申请的,实际上申请空间的时候是在页表上申请的,只要在页表中将虚拟地址进行初始化,然后把整个虚拟地址的起始地址返回给上层,那么对应进程地址空间也被开辟好了。然后将共享内存在内存中释放,就完成了共享内存的删除。

理解

操作系统需要对共享内存进行管理:
操作系统中有一对进程在进行进程间通信,那么也有可能有多对进程想进行进程间通信,那么操作系统中一定运行多个共享内存被创建,并且多个共享内存可以同时存在,那么这个每个共享内存的使用情况操作系统需要知道吧,所以操作系统需要对共享内存进行管理,也就是先描述再组织,当创建共享内存的时候,操作系统会为其创建对应的结构体对象,操作系统还可以创建很多共享内存,每个共享内存都为其创建对应的结构体,再将所有的结构体对象以链表的形式组织起来,那么操作系统对共享内存的管理就转化为了对链表的管理了。

由于操作系统中有多个共享内存,那么当一对进程需要进程间通信时,它们如何保证它们指向的是同一个共享内存的呢?那么就需要要求共享内存具有唯一标识,这就由延伸出两个问题:

  1. 这个标识怎么来 (用户自己生成传给共享内存)
  2. 怎么将标识交给另一个进程(通过约定的方式)

这两个问题的详细讲解和更多的原理会在使用中讲到。


3.2 共享内存函数

3.2.1 ftok函数

#include 
key_t ftok(const char *pathname, int proj_id);

功能:ftok函数是Linux系统中用于生成唯一键值(key)的函数

参数

  • pathname:指向系统中的一个现有文件或目录的路径名。这个文件或目录必须是有效的,为了项目的可读性,通常这个文件或目录是与项目是有关系的。
  • proj_id:项目标识符,通常为一个字符或整数。

返回值

  • 成功时,ftok函数返回一个key_t类型的唯一键值。
  • 失败时,返回-1,并设置errno来指示错误。

3.2.2 shmget函数

#include 
#include 	
int shmget(key_t key, size_t size, int shmflg);

功能:用于创建/获取共享内存

参数

  • key : 这是一个用于标识共享内存段的键,你可以给他任意值,但是可能会导致它更容易与其他的key冲突,所以这个键规定使用 ftok 函数生成的。
  • size:指定共享内存的大小,建议是4096的倍数,即使你给的大小不是4096的倍数,他在底层也会以4096的倍数进行创建,但是它只会返回你所需要的大小,多出来的部分你也不能使用。
  • shmflg :是一组标志位,通常是以下值的组合:
    • IPC_CREAT: 如果共享内存段不存在,则创建它,如果存在就直接返回共享内存的起始地址。
    • IPC_EXCL:它不能单独被使用,与 IPC_CREAT 一起使用时,如果共享内存段不存在,则创建共享内存,如果共享内存段已存在,出错返回。
    • 权限标志(如 0666),这些标志设置共享内存段的访问权限,类似于文件的权限设置。

返回值

  • 成功时,shmget 返回一个与共享内存段关联的标识符(shm_id)。
  • 失败时,返回 -1 并设置 errno 以指示错误类型。

3.2.3 shmat函数

#include 
#include 
void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:shmat函数是Linux系统中用于将共享内存区对象映射到调用进程的地址空间的函数。

参数

  • shmid:由shmget函数返回的共享内存标识符。

  • shmaddr:指定共享内存区对象在调用进程地址空间中的连接位置。由于我们对进程地址空间的使用并不熟悉,通常这里使用NULL让操作系统来帮我们选择连接的位置。

  • shmflg:标志参数,用于控制连接行为。

    • 如果指定了SHM_RDONLY标志,则以只读方式连接共享内存。
    • 否则,以读写方式连接共享内存。

返回值

  • 成功时,shmat函数返回共享内存区对象在调用进程地址空间中的实际连接地址。
  • 失败时,返回(void *)-1,并设置errno来指示错误。

3.2.4 shmdt函数

#include 
#include 
int shmdt(const void *shmaddr);

功能:shmdt 函数是 Linux系统中用于断开共享内存段与调用进程的地址空间之间的连接的函数。

参数

  • shmaddr:指向先前由 shmat 函数返回的共享内存区对象在调用进程地址空间中的连接地址。

返回值

  • 成功时,shmdt 函数返回 0。
  • 失败时,返回 -1,并设置 errno 来指示错误。

注意:将共享内存段与当前进程脱离不等于删除共享内存段


3.2.5 shmctl函数

#include 
#include 
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能:shmctl 函数是 Linux系统中用于控制共享内存段的一个函数。它允许进程对共享内存段进行各种控制操作,如获取状态、修改权限、删除共享内存段等。

参数

  • shmid:由 shmget 函数返回的共享内存标识符。

  • cmd:指定要执行的控制命令。常见的命令包括:

    • IPC_STAT:获取共享内存段的状态,将共享内存的 shmid_ds 结构复制到 buf 中。
    • IPC_SET:设置共享内存段的某些属性,将 buf 中的信息拷贝到内核空间以设置 IPC 内核对象。
    • IPC_RMID:删除共享内存段。此时,buf 参数被忽略,可以传 NULL。
  • buf:指向 shmid_ds 结构体的指针,用于存储或接收共享内存段的状态信息。当 cmd 为 IPC_STAT 时,buf 用于接收返回值;当 cmd 为 IPC_SET 时,buf 用于传递值并设置内核对象。

返回值

  • 成功时,shmctl 函数返回 0。
  • 失败时,返回 -1,并设置 errno 来指示错误。

3.3 共享内存的使用

进程间通信需要两个进程,我这里创建两个源文件,一个server.cpp代表服务端,client.cpp代表客户端。

3.3.1 ftok函数与shmget函数的使用

shmget函数的参数中有一个参数是key,它是为了创建共享内存时,用来标记共享内存的,为了减少冲突,操作系统中使用了一个特殊的算法来设计了一个ftok函数。

这里我创建一个comm.hpp文件,用于编写服务端和客户端都需要的函数和数据,我们将pathname和proj_id写入到.hpp文件中,使两个源文件都使用.hpp文件,那么两个源文件使用同样的算法,同样的pathname和同样的proj_id,形成的key值一定是相同的,那么两个进程最终就会指向同一个共享内存,这样就完成了进程间通信的前提:不同的进程看到同一份资源。

下面我们就使用ftok函数返回的key作为shmget的参数来创建共享内存,这里我使用共享内存不存在就创建,存在就出错返回的方式来创建,观察下面代码和运行结果,我们发现第一次创建共享内存时,创建成功并输出了shmid的值,我们可以使用ipcs -m的指令来查看操作系统中共享内存的属性,我们发现共享内存确实创建成功了,当我们第二次创建时却发现创建失败了,并且这时候进程也已经退出了,所以这里可以得出一个结论就是:共享内存的生命周期并不跟随进程,而是跟随内核,除非用户使用指令或是函数将共享内存删除。system V下的所有IPC资源的生命周期都跟随内核

上面我们查看共享内存属性的时候,发现有两个标识符:key和shmid。

  • key:不能在应用层使用,它只能用于内核中标记共享内存。
  • shmid:在应用层操作共享内存时使用shmid。

所以我们使用函数或是指令删除时,就需要用到shmid。

这里我先使用指令来删除共享内存ipcrm -m shmid

我们看到共享内存的属性中还有权限和共享内存的大小,这两个属性都是可以在创建的时候给共享内存设置的。这里设置共享内存大小时建议是4096的倍数,即使你给的大小不是4096的倍数,他在底层也会以4096的倍数进行创建,但是它只会返回你所需要的大小,多出来的部分你也不能使用。


3.3.2 shmat函数的使用

shmat函数能够将共享内存区对象映射到调用进程的地址空间中。我们看到共享内存中有一个属性叫做nattch,这个数据就是有多少个进程的地址空间与该共享内存相互映射。

观察下面代码、进程运行结果和查看内存空间属性的脚本结果,首先操作系统中没有共享内存,运行进程后出现了一个共享内存,但是与没有地址空间与之相互映射,它的nattch为0,过了三秒以后,进使用shmat函数将它的地址空间与地址空间相互映射,共享内存的nattch变为了1,再过三秒后进程退出,共享内存的nattch又变回了0。


3.3.3 shmdt函数的使用

shmdt 函数能够断开共享内存段与调用进程的地址空间之间的连接。它的参数就是shmat的返回值。

观察下面代码、进程运行结果和查看内存空间属性的脚本结果,首先操作系统中没有共享内存,运行进程后出现了一个共享内存,但是与没有地址空间与之相互映射,它的nattch为0,过了三秒以后,进程使用shmat函数将它的地址空间与地址空间相互映射,共享内存的nattch变为了1,再过三秒后进程使用shmdt函数断开地址空间与共享内存的映射关系,共享内存的nattch又变回了0,再过三秒后进程退出。


3.3.4 shmctl函数的使用

shmctl函数能够控制共享内存段。它允许进程对共享内存段进行各种控制操作,如获取状态、修改权限、删除共享内存段等。

3.3.4.1 删除共享内存

我这里使用函数的删除功能,观察下面代码、进程运行结果和查看内存空间属性的脚本结果,首先操作系统中没有共享内存,运行进程后出现了一个共享内存,但是与没有地址空间与之相互映射,它的nattch为0,过了三秒以后,进使用shmat函数将它的地址空间与地址空间相互映射,共享内存的nattch变为了1,再过三秒后进程使用shmdt函数断开地址空间与共享内存的映射关系,共享内存的nattch又变回了0,再过三秒删除共享内存,我们确实也没查到刚刚的共享内存,再过三秒后进程退出。


3.3.4.2 获取共享内存中的属性

通过查询shmctl函数,我们可以将shmid_ds对象作为shmctl的参数,当调用完函数后,shmid_ds对象中就存储着部分共享内存的属性,我们还发现shmid_ds结构体中还存储着另一个ipc_perm结构体。我们可以将结构体中的属性打印出来,看看与指令查找出来的共享内存属性是否一致。

通过上面进程的运行结果和指令的运行结果,我们发现shmid_ds对象中确实存储的是共享内存的属性,既然能获取到共享内存的属性,就代表我们上面说的是正确的,操作系统中为共享内存维护了一个结构体对象用来存储共享内存的属性的。共享内存 = 共享内存空间 + 共享内存属性。


3.3.5 进行通信

server端在创建共享内存时,就已经将约定好的key放入到了共享内存中了,client端只需要通过约定的key就可以找到对应的共享内存了。

观察下面代码、进程运行结果和查看内存空间属性的脚本结果,首先操作系统中没有共享内存,运行server端后出现了一个共享内存,但是与没有地址空间与之相互映射,它的nattch为0,然后运行client端,client端使用shmat函数将它的地址空间与地址空间相互映射,共享内存的nattch变为了1,过了几秒以后,server端使用shmat函数将它的地址空间与地址空间相互映射,共享内存的nattch变为了2,再过几秒后client端使用shmdt函数断开地址空间与共享内存的映射关系,共享内存的nattch又变回了1,server端使用shmdt函数断开地址空间与共享内存的映射关系,共享内存的nattch又变回了0,再过几秒后进程退出。



上面所写的这些代码并没有让两个进程进行通信,我们写了这么多代码也只是为两个进程看到同一份资源,所以进程间通信无论是共享内存还是管道,它本质的叫法是:为两个进程通信做准备,让两个进程看到同一份资源

那么这里是如何让两个进程看到同一份资源的呢?
根据两个进程相同且唯一的key看到同一份资源的,而命名管道是通过路径+文件名来使两个进程看到同一份资源的,路径+文件名也具有唯一性,共享内存和命名管道都是通过唯一性使两个进程看到同一份资源的。

为什么key是由用户生成的而不让操作系统生成呢?
假设是由操作系统生成,那么server端进行创建共享内存后返回shmid就可以直接使用了,但是client端想找到对应的共享内存时,操作系统内有很多共享内存,在系统层面上key只有操作系统和创建该共享内存的进程知道,而想要其他进程根本不可能知道它的key,那么client端就不知道对应的共享内存是哪个了。这里让用户生成key的根本原因就是让用户的不同进程看到同一个key。


这里我们将使用server端和client端进行通信。共享内存是支持让两个进程进行同时通信的,它们通信的方式取决于你的代码是如何编写的。

这里我们让client端3秒向共享内存中写入一次,让server端1秒从共享内存中读一次,先运行server端,过几秒在运行client端,观察下面的代码和运行结果,我们发现在client端还没有运行起来的时候,server端就已经开始从共享内存中读取数据了,当运行client端后,client端写入了一次,server端就读取了3次。得出结论:共享内存的通信方式不会提供同步机制,共享内存是直接裸露给使用者的,所以一定要注意共享内存的使用安全。



我们上面学习了管道,管道是提供了同步的机制的,这里我们通过管道来为共享内存添加同步机制。

我们在server端中添加了创建命名管道和向管道中读取的步骤,在client端中添加了向管道中写入的步骤。

运行server端时,创建命名管道,创建命名空间并与地址空间进行映射,在进程想读取共享内存之前,我们添加向管道中读取的操作,只有从管道中读到信息才证明了client端向共享内存中写入了数据,server端才能读取共享内存中的数据。

运行client端时,找到并使自己的进程地址空间与对应的共享内存映射,然后向共享内存中写入数据,当向共享内存中写入完毕后,再向管道中写入数据,代表client端已经写入数据,server端可以从共享内存中读取。

从下面的运行结果来看,确实为共享内存添加了同步机制,但是server端的读取却并没有真正的将数据读走,而是将数据拷贝出去了,如果想实现真正意义上的读走,可以在共享内存的开头添加管理字段,这里就不做讲解了。

// comm.hpp
#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const char *pathname = "/home/chineseperson04/CSDN/IPC/shared_memory";
int proj_id = 9;
const char *filename = "fifo";

const int size = 4096;

int GetKey()
{
    int key = ftok(pathname, proj_id);
    if (key == -1)
    {
        cout << "errno:" << errno << " strerror:" << strerror(errno) << endl;
        exit(2);
    }
    return key;
}

string ToHex(int key)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0x%x", key);
    return buffer;
}

int ShmHelper(int key, int flag)
{
    int shmid = shmget(key, size, flag);
    if (shmid == -1)
    {
        cout << "errno:" << errno << " strerror:" << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}

int CreateShm(int key)
{
    return ShmHelper(key, IPC_CREAT | IPC_EXCL | 0666);
}

int GetShm(int key)
{
    return ShmHelper(key, IPC_CREAT);
}

// 创建命名管道
bool MakeFifo()
{
    int n = mkfifo(filename, 0666);
    if (n < 0)
    {
        cerr << "errno" << errno << "strerror" << strerror(errno) << endl;
        return false;
    }
    cout << "create fifo success..." << endl;
    return true;
}
// server.cpp
#include 
#include "comm.hpp"

using namespace std;

int main()
{
    // 创建管道失败则直接退出
    if (!MakeFifo())
        return 1;
    int key = GetKey();
    // cout << "key:" << ToHex(key) << endl;

    int shmid = CreateShm(key);
    // cout << "shmid:" << shmid << endl;
    // sleep(3);

    char *s = (char *)shmat(shmid, nullptr, 0);
    cout << "开始将shm映射到进程地址空间中..." << endl;
    // sleep(3);

    int fd = open(filename, O_RDONLY);

    while (true)
    {
        int code = 0;
        // 如果client端没有写入,这里read就会一直等待
        // 直到client端写入后,向管道中发送提示后再进行读取
        ssize_t n = read(fd, &code, sizeof(code));
        if (n == 4)
        {
            cout << "共享内存中的内容:";
            cout << s << endl;
        }
        // 如果n == 0,代表client端已经关闭,这里也直接退出
        else if (n == 0)
        {
            return 2;
        }
    }

    shmdt(s);
    cout << "开始将shm从进程地址空间中移除..." << endl;
    // sleep(3);

    shmctl(shmid, IPC_RMID, nullptr);
    cout << "开始将shm从操作系统中删除" << endl;
    // sleep(3);

    close(fd);
    // unlink可以删除指定路径下的文件,这里删除管道文件
    unlink(filename);

    return 0;
}
// client.cpp
#include 
#include "comm.hpp"
#include 

using namespace std;

int main()
{
    int key = GetKey();
    int shmid = GetShm(key);

    char *s = (char *)shmat(shmid, NULL, 0);
    cout << "attach shm done" << endl;
    // sleep(5);

    int fd = open(filename, O_WRONLY);
    char c = 'a';

    for (; c <= 'z'; c++)
    {
        s[c - 'a'] = c;
        cout << "write " << c << " done" << endl;

        // 通知对方
        int code = 0;
        write(fd, &code, sizeof(code));
        sleep(3);
    }

    shmdt(s);
    cout << "detach shm done" << endl;

    close(fd);

    return 0;
}

3.4 共享内存总结

  1. 共享内存的通信方式不会提供同步机制,共享内存是直接裸露给使用者的,所以一定要注意共享内存的使用安全
  2. 共享内存可以提供较大的空间。
  3. 共享内存是所有进程间通信中最快的。

如何理解共享内存是所有进程间通信中最快的呢?
例如共享内存与管道,共享内存相比于管道减少数据进程通信时的拷贝,以下面一个具体的例子为例,从键盘中输入数据,通过A、B进程将数据打印到显示屏中。我们需要知道的一个知识:凡是数据迁移都是拷贝

通过管道则需要4次拷贝,这还是不算上输入输出函数中缓冲区的结果,将数据拷贝到进程A的用户缓冲区,将进程A用户缓冲区的数据拷贝到管道中,从管道中将数据拷贝到进程B的用户缓冲区中,再将进程B用户缓冲区中是数据拷贝到显示器中。

通过共享内存则需要2次,将数据拷贝到共享内存中,再将共享内存中的数据拷贝到显示器中。

根据这个例子来看共享内存至少减少了两次拷贝。需要注意:减少拷贝的次数,一定要在确定的场景中才能被确定


四、system V消息队列(略讲)

4.1 消息队列的原理

消息队列与共享内存的原理十分相似,无非就是变为在物理内存中申请一个队列,消息队列的底层是链表结构,消息队列的头部(msg_first)和尾部(msg_last)指针用于维护这个链表结构。使用者必须在每个进程中定义一个结构体,里面必须有一个字段用来使消息队列区分是哪个进程写入的,方便另一个进程进行读取。

操作系统中会有许多消息队列,操作系统就需要对消息队列进行管理,先描述再组织,当创建消息队列的时候,操作系统会为其创建对应的结构体对象,操作系统还可以创建很多消息队列,每个消息队列都为其创建对应的结构体,再将所有的结构体对象以链表的形式组织起来,那么操作系统对消息队列的管理就转化为了对链表的管理了。消息队列=队列+队列属性。通过对消息队列结构体的查看,我们发现消息队列中的第一个属性与共享内存中的第一个属性相同都是一个结构体ipc_perm。


4.2 消息队列函数

4.2.1 msgget 函数

#include 
#include 
#include 
int msgget(key_t key, int msgflg);

功能:msgget 函数用于创建一个新的消息队列或获取一个现有消息队列的标识符。

参数

  • key : 这是一个用于标识消息队列的键,你可以给他任意值,但是可能会导致它更容易与其他的key冲突,所以这个键规定使用 ftok 函数生成的。
  • shmflg :是一组标志位,通常是以下值的组合:
    • IPC_CREAT: 如果消息队列不存在,则创建它,如果存在就直接返回消息队列的起始地址。
    • IPC_EXCL:它不能单独被使用,与 IPC_CREAT 一起使用时,如果消息队列不存在,则创建消息队列,如果消息队列已存在,出错返回。
    • 权限标志(如 0666),这些标志设置消息队列的访问权限,类似于文件的权限设置。

返回值

  • 成功时,shmget 返回一个与消息队列关联的标识符(shm_id)。
  • 失败时,返回 -1 并设置 errno 以指示错误类型。

4.2.2 msgsnd函数

#include 
#include 
#include 
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

功能:用于向消息队列发送消息。

参数

  • msqid:由 msgget 返回的消息队列标识符。
  • msgp:指向要发送的消息的指针。消息结构必须以 long int 类型成员变量开始,用于指定消息类型。
  • msgsz:消息的长度(不包括消息类型成员变量的长度)。
  • msgflg:控制消息队列满或达到系统限制时的行为,可以包含 IPC_NOWAIT 标志。

返回值:成功时返回 0,失败时返回 -1 并设置 errno。


4.2.3 msgrcv函数

#include 
#include 
#include 
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

功能:用于从消息队列接收消息。

参数

  • msqid:由 msgget 返回的消息队列标识符。
  • msgp:指向用于接收消息的缓冲区的指针。
  • msgsz:缓冲区的长度。
  • msgtyp:用于指定接收消息的类型。
  • msgflg:控制消息队列中没有相应类型的消息时的行为,可以包含 IPC_NOWAIT 和 MSG_NOERROR 标志。

返回值

  • 成功时返回接收到的消息长度。
  • 失败时返回 -1 并设置 errno。

4.2.4 msgctl函数

#include 
#include 
#include 
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

功能:用于控制消息队列,如获取状态、设置属性或删除消息队列。

参数

  • msqid:由 msgget 返回的消息队列标识符。
  • cmd:指定要执行的操作,可以是 IPC_STAT、IPC_SET 或 IPC_RMID。
  • buf:指向 msqid_ds 结构的指针,用于存储或设置消息队列的属性。

返回值

  • 成功时返回 0。
  • 失败时返回 -1 并设置 errno。

五、system V信号量

5.1 预备知识

我们知道进程间通信的前提是不同的执行流(进程/线程)看到同一份资源,这份资源我们称之为公共资源,若该公共资源被多个执行流同时写入或同时读取,会导致数据不一致的问题,所以我们需要将这个公共资源保护起来,可以通过同步和互斥的方式将公共资源保护起来。保护资源无法就是用户和操作系统进行保护,操作系统保护公共资源,例如:匿名/命名管道和消息队列,用户保护公共资源,例如:共享内存。

  • 公共资源:多个执行流看到的同一份资源。
  • 同步:多个执行流执行时,按照一定的顺序执行。
  • 互斥:任何一个时刻只允许一个执行流访问资源。
  • 临界资源:被保护起来的公共资源,反之则是非临界资源
  • 临界区: 访问该临界资源的代码,反之则是非临界区。
  • 原子性:代表状态只有两种,要么做完,要么没做,它是没有中间态的

我们常说的维护临界资源,其实就是在维护临界区。


5.2 信号量的原理

这里我以一个生活中的例子开始讲解信号量。

不知道大家有没有去电影院看过电影,在电影院中每一场电影都有很多的座位,这个座位是否是你的取决于你买到了这个位置的票还是你坐在了这个座位上?在现实中,只要你买到票了这个座位就属于你了,除非有没素质的人强制坐了你的位置,这时候你去找管理人员,这个位置依旧是你的。电影院和内部的座位就是多个人共享的资源,在现实生活中我们称之为公共资源,我们刚刚讲过只要买到了票,这个位置就属于你,即使你没有去,它也需要将这个位置为你保留下来。我们买票的本质就是对资源的预定机制。假设电影院中有100个座位,那么它就不可能卖出101张票,通过一个计数器就可以做到,每卖出去一张票计数器就减一,当计算器为0后就代表票卖完了。这里的计数器就代表了公共资源的个数,一个公共资源(电影院)可以被拆分成为多分资源(座位)去使用。这种用来衡量公共资源数目,用来达到对公共资源进行分配目的的计数器我们称之为信号量。

信号量表示资源数目的计数器,每一个执行流想要访问公共资源中的某一份资源之前,需要先申请信号量资源,就是对信号量计数器进行减减操作。本质上只要减减成功,就完成了资源的预定。如果是申请不成功,这个执行流就要被挂起阻塞。

资源可以申请,同样也可以释放,我们说的维护临界资源,也就是在维护临界区,就是在临界区之前加上申请信号量资源,申请成功信号量计数器减减,代表申请资源成功,在临界区之后让信号量计数器加加,代表资源释放。

如果是公共资源只有一份,我们就可以将信号量计数器设置为1,在未来计数器就只能为0或是1,任何情况下就只有一个执行流可以访问这个资源,这就完成了一个互斥功能,我们将这样只有两态的信号量称之为二元信号量,在未来可以实现互斥锁,完成互斥功能

一个公共资源可以单独使用,那么信号量计数器就为1,一个公共资源也可以被分为n份,那么信号量计数器就是n,这样就可以使最多n个执行流使用公共资源时,只要每个执行流使用的资源是不同份就可以做到并发访问。

细节问题

  1. 申请共享资源之前需要先访问信号量资源,那么每个执行流就必须先看到同一份信号量资源,所以信号量资源就必须由操作系统提供,它也属于IPC体系。所以进程间通信并不只是传输数据为目的,让不同进程看到同一个计数器达到协同也是进程间通信的目的。
  2. 创建信号量的目的就是为了保护公共资源,可是信号量本质上就是公共资源,由于信号量访问方式并不复杂,只有加加和减减操作,它在内部中实现时,加加减减都是原子的,所以多个执行流在申请信号量资源时,并不会出现问题,我们将申请信号量(原子的减减)称之为P操作,释放信号量(原子的加加)称之为V操作。
  3. 如何理解申请信号量失败后,进程被阻塞挂起呢?我们可以简单的理解为单个信号量结构体中有一个计数器和等待队列(task_struct* queue),申请失败后就将进程从运行队列中放到等待队列中,申请成功后再将进程放回到运行队列中。

操作系统中会有信号量,操作系统就需要对信号量进行管理,先描述再组织,当创建信号量的时候,操作系统会为其创建对应的结构体对象,操作系统还可以创建很多信号量,每个信号量都为其创建对应的结构体,再将所有的结构体对象以链表的形式组织起来,那么操作系统对信号量的管理就转化为了对链表的管理了。通过信号量属性的查看,我们发现信号量的第一个成员也是一个ipc_perm结构体对象。

更多信号量的知识会在后面的多线程中讲到。


5.2 信号量函数

5.3.1 semget函数

#include 
#include 
#include 
int semget(key_t key, int nsems, int semflg);

功能: 创建一个新的信号量集或访问一个已存在的信号量集。

参数

  • key : 这是一个用于标识信号量集的键,你可以给他任意值,但是可能会导致它更容易与其他的key冲突,所以这个键规定使用 ftok 函数生成的。
  • nsems: 信号量集中信号量的数量。
  • shmflg :是一组标志位,通常是以下值的组合:
    • IPC_CREAT: 如果信号量集不存在,则创建它,如果存在就直接返回信号量集的起始地址。
    • IPC_EXCL:它不能单独被使用,与 IPC_CREAT 一起使用时,如果信号量集不存在,则创建信号量集,如果信号量集已存在,出错返回。
    • 权限标志(如 0666),这些标志设置信号量集的访问权限,类似于文件的权限设置。

返回值:

  • 成功时返回信号量集的标识符(semid)。
  • 失败时返回-1并设置errno。

5.3.2 semctl函数

#include 
#include 
#include 
int semctl(int semid, int semnum, int cmd, ...);

功能: 对信号量集执行各种控制操作,如初始化信号量、获取信号量值、设置信号量值等。

参数:

  • semid: 信号量集的标识符。
  • semnum: 信号量集中要操作的信号量的索引。
  • cmd: 要执行的控制操作。
  • : 根据cmd的不同,可能需要额外的参数。

返回值:根据cmd的不同,返回值可能不同。通常,成功时返回0或正数,失败时返回-1并设置errno。


5.3.3 semop函数

#include 
#include 
#include 
int semop(int semid, struct sembuf *sops, size_t nsops);

功能: 对信号量集中的信号量进行P(等待)和V(信号)操作。

参数:

  • semid: 信号量集的标识符。
  • sops: 指向sembuf结构数组的指针,每个结构指定一个信号量及其要执行的操作。
  • nsops: sops数组中的元素数量。

sembuf结构通常定义如下:

struct sembuf {
    unsigned short sem_num;  // 信号量在信号量集中的索引
    short sem_op;           // 要执行的操作(正数表示V操作,负数表示P操作,0表示获取信号量的当前值)
    short sem_flg;          // 操作标志(通常为0或IPC_NOWAIT)
};

返回值:

  • 成功时返回0。
  • 失败时返回-1并设置errno。

六、内核是如何看待IPC资源的

system V版本下所有描述IPC资源的结构体都叫做xxxid_ds,并且他们的第一个成员都是结构体ipc_perm,这些结构都是用户级别的,也是操作系统给用户暴露出来的属性。

内核是如何看待IPC资源的:

  1. IPC资源是被单独设计出来的
  2. 内核中维护IPC资源的方式

首先操作系统层面能够找到一个结构体ipc_lds,这个结构体中有一个成员entries指向结构体ipc_id_array,结构体ipc_id_array有一个成员记录数组元素,还有一个变长数组,数组中存储的都是korn_ipc_perm结构体的指针。在描述共享内存、消息队列和信号量的结构体中,它们的第一个成员都是korn_ipc_perm结构体,我们知道结构体中第一个成员的地址就是结构体的地址,ipc_id_array结构体中存储着所以IPC资源的结构体中的第一个成员的地址,那么操作系统就可以根据这些地址找到所以的IPC资源的结构体,所以操作系统可以通过该数组管理所有的IPC资源,虽然数组中指针只有资格访问IPC资源的结构体中前面一部分的数据,但是操作系统知道指针指向的是哪一种IPC资源,通过对类型的强制类型转换,就可以访问到IPC资源的结构体中所有的数据了。

通过上面的解释,我们发现这种方式很像多态,kern_ipc_perm就是基类,其他IPC资源的结构体都是子类,初始化子类对象时,会先将基类部分先初始化。


结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

本文地址:https://www.vps345.com/3892.html

搜索文章

Tags

PV计算 带宽计算 流量带宽 服务器带宽 上行带宽 上行速率 什么是上行带宽? CC攻击 攻击怎么办 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP 服务器 linux 运维 游戏 云计算 javascript 前端 chrome edge deepseek Ollama 模型联网 API CherryStudio 进程 操作系统 进程控制 Ubuntu python MCP 阿里云 网络 网络安全 网络协议 llama 算法 opencv 自然语言处理 神经网络 语言模型 ssh ubuntu Flask FastAPI Waitress Gunicorn uWSGI Uvicorn RTSP xop RTP RTSPServer 推流 视频 科技 ai java 人工智能 个人开发 harmonyos 华为 开发语言 typescript 计算机网络 数据库 centos oracle 关系型 安全 分布式 c# macos adb YOLO efficientVIT YOLOv8替换主干网络 TOLOv8 flutter Hyper-V WinRM TrustedHosts android 鸿蒙 php 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 Dell R750XS 华为云 物联网 嵌入式硬件 单片机 c++ 温湿度数据上传到服务器 Arduino HTTP udp unity 面试 性能优化 jdk intellij-idea 架构 vue.js audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 学习 rust http uni-app 深度学习 目标检测 计算机视觉 fastapi mcp mcp-proxy mcp-inspector fastapi-mcp agent sse flask spring boot AI编程 AIGC pycharm ide pytorch filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 ssl 前端框架 golang 后端 HCIE 数通 numpy tcp/ip GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 vue3 HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 asm springsecurity6 oauth2 授权服务器 token sas 笔记 C 环境变量 进程地址空间 github 创意 社区 docker 容器 DeepSeek-R1 API接口 live555 源码剖析 rtsp实现步骤 流媒体开发 kubernetes k8s 运维开发 持续部署 vim list 数据结构 远程控制 远程看看 远程协助 rsyslog cpu 内存 实时 使用 vscode 电脑 经验分享 word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 C语言 WSL2 VMware安装Ubuntu Ubuntu安装k8s DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 apache windows 产品经理 agi microsoft 僵尸进程 YOLOv8 NPU Atlas800 A300I pro asi_bench 信息与通信 系统架构 mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 ESP32 camera Arduino 电子信息 开源 统信UOS 麒麟 bonding 链路聚合 nginx c语言 qt stm32项目 stm32 debian PVE prometheus iot Ubuntu Server Ubuntu 22.04.5 Linux Windsurf 飞牛NAS 飞牛OS MacBook Pro rabbitmq 安装教程 GPU环境配置 Ubuntu22 CUDA PyTorch Anaconda安装 websocket react.js 前端面试题 node.js 机器学习 oceanbase rc.local 开机自启 systemd redis mybatis chatgpt 大模型 llama3 Chatglm 开源大模型 编辑器 transformer ping++ conda 深度优先 图论 并集查找 换根法 树上倍增 ddos ollama llm 学习方法 zotero WebDAV 同步失败 代理模式 ffmpeg 音视频 mcu 大数据 爬虫 数据挖掘 网络用户购物行为分析可视化平台 大数据毕业设计 1024程序员节 测试工具 web安全 银河麒麟服务器操作系统 系统激活 sql KingBase 博客 windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 负载均衡 Qwen2.5-coder 离线部署 mac 漏洞 Dify 智能手机 NAS Termux Samba tomcat postman mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 YOLOv12 LDAP aws googlecloud ecmascript KVM pip 腾讯云 maven intellij idea fpga开发 鸿蒙系统 Cookie 实时音视频 AI Agent dubbo 计算机外设 软件需求 r语言 数据可视化 数据分析 .netcore git ansible cuda cudnn anaconda mysql 微服务 springcloud kamailio sip VoIP 大数据平台 .net sqlserver mongodb rust腐蚀 嵌入式 linux驱动开发 arm开发 进程信号 豆瓣 追剧助手 迅雷 nas 微信 微信分享 Image wxopensdk gitlab firewalld 低代码 jenkins kafka jmeter 软件测试 多线程服务器 Linux网络编程 监控 自动化运维 gateway Clion Nova ResharperC++引擎 Centos7 远程开发 gitee 智能路由器 外网访问 内网穿透 端口映射 ollama下载加速 pillow HarmonyOS Next rtsp rtp json html5 firefox WSL win11 无法解析服务器的名称或地址 https 命名管道 客户端与服务端通信 django web3.py 小程序 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 自动化 蓝耘科技 元生代平台工作流 ComfyUI Kali Linux 黑客 渗透测试 信息收集 程序人生 gpu算力 代码调试 ipdb 群晖 文件分享 中间件 iis VSCode postgresql NPS 雨云服务器 雨云 游戏程序 DeepSeek 环境迁移 崖山数据库 YashanDB 云原生 WebUI DeepSeek V3 AI大模型 cmos 硬件 Ubuntu 24.04.1 轻量级服务器 MQTT协议 消息服务器 代码 NFS redhat zabbix spring selenium dba tidb GLIBC pdf 毕设 jar 微信小程序 服务器数据恢复 数据恢复 存储数据恢复 raid5数据恢复 磁盘阵列数据恢复 elasticsearch Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 VMware安装mocOS VMware macOS系统安装 shell 系统 黑苹果 虚拟机 kylin 银河麒麟操作系统 国产化 pygame 小游戏 五子棋 rpc 远程过程调用 Windows环境 sdkman 直播推流 服务器部署ai模型 sqlite3 Anolis nginx安装 环境安装 linux插件下载 软件工程 pyautogui 硬件工程 svn 缓存 三级等保 服务器审计日志备份 并查集 leetcode nvidia kvm 无桌面 命令行 串口服务器 媒体 微信公众平台 risc-v 3d gcc 数据集 ipython 软考 jupyter 英语 mysql离线安装 ubuntu22.04 mysql8.0 html css spring cloud 源码 毕业设计 课程设计 QQ 机器人 bot Docker 系统开发 binder 车载系统 framework 源码环境 联想开天P90Z装win10 混合开发 JDK matlab gpt bootstrap web Socket ci/cd 京东云 压力测试 k8s集群资源管理 云原生开发 基础入门 编程 hadoop 网工 压测 ECS hive Hive环境搭建 hive3环境 Hive远程模式 Linux PID Cline 自动化编程 宕机切换 服务器宕机 Deepseek centos-root /dev/mapper yum clean all df -h / du -sh LInux ai小智 语音助手 ai小智配网 ai小智教程 智能硬件 esp32语音助手 diy语音助手 idm 远程工作 flash-attention 报错 ros2 moveit 机器人运动 java-ee Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 图像处理 eureka 数据库架构 数据管理 数据治理 数据编织 数据虚拟化 webrtc iDRAC R720xd docker run 数据卷挂载 交互模式 okhttp 能力提升 面试宝典 技术 IT信息化 wireshark 显示过滤器 安装 ICMP Wireshark安装 华为od playbook thingsboard 交换机 telnet 远程登录 unix tcpdump 磁盘监控 RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 具身智能 技能大赛 rime 相差8小时 UTC 时间 Java netty dell服务器 go 蓝桥杯 devops IIS .net core Hosting Bundle .NET Framework vs2022 XFS xfs文件系统损坏 I_O error es jvm cnn file server http server web server 集成学习 集成测试 eNSP 网络规划 VLAN 企业网络 状态管理的 UDP 服务器 Arduino RTOS yum 企业微信 Linux24.04 deepin 服务器配置 生物信息学 openEuler Reactor 设计模式 C++ linux环境变量 驱动开发 gitea minio C# MQTTS 双向认证 emqx 计算机 监控k8s集群 集群内prometheus Invalid Host allowedHosts vue 程序员 远程连接 rdp 实验 gradle 王者荣耀 k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm Redis Desktop minicom 串口调试工具 DNS express UOS 统信操作系统 SysBench 基准测试 iftop 网络流量监控 ecm bpm 向日葵 Docker Compose docker compose docker-compose make命令 makefile文件 云电竞 云电脑 todesk LLM 职场和发展 设备 GPU PCI-Express MNN Qwen matplotlib ip ui selete 高级IO TRAE 音乐服务器 Navidrome 音流 政务 分布式系统 监控运维 Prometheus Grafana 安卓 游戏服务器 Minecraft docker命令大全 5G 3GPP 卫星通信 iBMC UltraISO Erlang OTP gen_server 热代码交换 事务语义 文件系统 路径解析 safari Mac ESXi Dell HPE 联想 浪潮 gpt-3 文心一言 bash freebsd 系统安全 显卡驱动 部署 远程桌面 DeepSeek行业应用 Heroku 网站部署 excel 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 kind AI写作 AI作画 react next.js 部署next.js 聊天室 MacOS录屏软件 etcd 数据安全 RBAC mamba Vmamba ocr 命令 金融 硬件架构 思科模拟器 思科 Cisco ceph 测试用例 功能测试 ruoyi asp.net大文件上传 asp.net大文件上传源码 ASP.NET断点续传 asp.net上传文件夹 asp.net上传大文件 .net core断点续传 .net mvc断点续传 剧本 muduo X11 Xming rocketmq 大模型面经 大模型学习 RAGFLOW RAG 检索增强生成 文档解析 大模型垂直应用 其他 visual studio code ip命令 新增网卡 新增IP 启动网卡 dify 深度求索 私域 知识库 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 mariadb docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos 弹性计算 虚拟化 计算虚拟化 弹性裸金属 springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 腾讯云大模型知识引擎 DevEco Studio 医疗APP开发 app开发 工业4.0 安全威胁分析 vscode 1.86 网站搭建 serv00 jetty undertow Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 grafana 直流充电桩 充电桩 微信开放平台 微信公众号配置 IPMI threejs 3D RAID RAID技术 磁盘 存储 远程 执行 sshpass 操作 银河麒麟 kylin v10 麒麟 v10 EMUI 回退 降级 升级 SSH Xterminal Playwright 自动化测试 JAVA 云服务器 裸金属服务器 弹性裸金属服务器 kali 共享文件夹 p2p linux上传下载 多进程 游戏机 hugo Netty 即时通信 NIO SWAT 配置文件 服务管理 网络共享 vmware 卡死 IIS服务器 IIS性能 日志监控 半虚拟化 硬件虚拟化 Hypervisor 单元测试 micropython esp32 mqtt nuxt3 灵办AI 边缘计算 Trae AI代码编辑器 图形化界面 sqlite pgpool 开机自启动 virtualenv openssl 密码学 业界资讯 模拟退火算法 服务器管理 宝塔面板 配置教程 服务器安装 网站管理 田俊楠 code-server MQTT mosquitto 消息队列 Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer ue4 着色器 ue5 虚幻 目标跟踪 OpenVINO 推理应用 n8n 工作流 workflow 数据库系统 wsl2 wsl hibernate tensorflow trae GCC crosstool-ng echarts 信息可视化 网页设计 客户端 同步 备份 建站 大模型入门 大模型教程 remote-ssh 国产操作系统 ukui 麒麟kylinos openeuler W5500 OLED u8g2 TCP服务器 多层架构 解耦 chfs ubuntu 16.04 统信 虚拟机安装 框架搭建 unity3d CLion IDE 网络穿透 火绒安全 VPS Nuxt.js 分析解读 bug Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 RTMP 应用层 致远OA OA服务器 服务器磁盘扩容 CPU 主板 电源 网卡 OD机试真题 华为OD机试真题 服务器能耗统计 CORS 跨域 孤岛惊魂4 uniapp 恒源云 curl wget vSphere vCenter 软件定义数据中心 sddc 实时互动 springboot 北亚数据恢复 oracle数据恢复 IPMITOOL BMC 硬件管理 arm 设置代理 实用教程 opcua opcda KEPServer安装 MacMini 迷你主机 mini Apple oneapi 大模型微调 宠物 免费学习 宠物领养 宠物平台 open webui 小艺 Pura X linux 命令 sed 命令 传统数据库升级 银行 大语言模型 LLMs 华为认证 网络工程师 繁忙 服务器繁忙 解决办法 替代网站 汇总推荐 AI推理 移动云 MS Materials android studio 鲲鹏 FTP 服务器 可信计算技术 安全架构 网络攻击模型 ragflow XCC Lenovo embedding visualstudio VR手套 数据手套 动捕手套 动捕数据手套 SSL 域名 skynet 输入法 av1 电视盒子 机顶盒ROM 魔百盒刷机 LORA NLP 数学建模 AI 原生集成开发环境 Trae AI SSH 服务 SSH Server OpenSSH Server 嵌入式实习 RustDesk自建服务器 rustdesk服务器 docker rustdesk 黑客技术 流式接口 URL 本地部署 api 线程 pyqt protobuf 序列化和反序列化 Kylin-Server 指令 EasyConnect webstorm mq nextjs reactjs 强制清理 强制删除 mac废纸篓 chrome devtools chromedriver Google pay Apple pay opensearch helm 服务器主板 AI芯片 搜索引擎 ssrf 失效的访问控制 ArcTS 登录 ArcUI GridItem ios arkUI 交互 WebRTC openwrt ux 多线程 open Euler dde Python 网络编程 聊天服务器 套接字 TCP Ark-TS语言 Cursor xrdp string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap Docker Hub docker pull 镜像源 daemon.json TrinityCore 魔兽世界 xml adobe 监控k8s 监控kubernetes elk 语法 npm 云服务 odoo 服务器动作 Server action 软件构建 视觉检测 虚拟显示器 办公自动化 自动化生成 pdf教程 VMware创建虚拟机 视频编解码 dash 正则表达式 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 捆绑 链接 谷歌浏览器 youtube google gmail 重启 排查 系统重启 日志 原因 CrewAI log4j edge浏览器 ruby arcgis qemu libvirt 图形渲染 WebVM 僵尸世界大战 游戏服务器搭建 IM即时通讯 剪切板对通 HTML FORMAT 流水线 脚本式流水线 影刀 #影刀RPA# saltstack g++ g++13 ros DenseNet Ubuntu共享文件夹 共享目录 Linux共享文件夹 c swoole alias unalias 别名 FTP服务器 v10 软件 运维监控 阿里云ECS 增强现实 沉浸式体验 应用场景 技术实现 案例分析 AR figma prompt 架构与原理 xcode 无人机 推荐算法 多个客户端访问 IO多路复用 回显服务器 TCP相关API regedit 开机启动 代理 网络药理学 生信 gromacs 分子动力学模拟 MD 动力学模拟 tailscale derp derper 中转 triton 模型分析 cd 目录切换 less 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 大文件秒传跨域报错cors C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 webgl 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? 本地部署AI大模型 IDEA GoogLeNet 考研 lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 cursor apt 国内源 lua MCP server C/S windows日志 vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 私有化 DOIT 四博智联 Unity Dedicated Server Host Client 无头主机 aarch64 编译安装 HPC Deepseek-R1 私有化部署 推理模型 域名服务 DHCP 符号链接 配置 性能测试 dity make DIFY 常用命令 文本命令 目录命令 python3.11 Open WebUI epoll tcp 实战案例 searxng midjourney 前后端分离 移动魔百盒 langchain deep learning USB转串口 CH340 sentinel 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 RAGFlow 本地知识库部署 DeepSeek R1 模型 邮件APP 免费软件 反向代理 佛山戴尔服务器维修 佛山三水服务器维修 HarmonyOS firewall cfssl wordpress 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 实习 FunASR ASR 匿名管道 Spring Security 技术共享 我的世界 我的世界联机 数码 deepseek r1 我的世界服务器搭建 Wi-Fi Typore 干货分享 黑客工具 密码爆破 链表 rclone AList webdav fnOS 执法记录仪 智能安全帽 smarteye iphone 矩阵 线性代数 电商平台 ROS 自动驾驶 服务器时间 聚类 ISO镜像作为本地源 eclipse 虚拟局域网 Linux的基础指令 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 强化学习 EtherCAT转Modbus ECT转Modbus协议 EtherCAT转485网关 ECT转Modbus串口网关 EtherCAT转485协议 ECT转Modbus网关 nac 802.1 portal DocFlow H3C powerpoint 历史版本 下载 pppoe radius etl gaussdb dns是什么 如何设置电脑dns dns应该如何设置 音乐库 飞牛 xss 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 交叉编译 Linux环境 序列化反序列化 主从复制 人工智能生成内容 网络结构图 yaml Ultralytics 可视化 AI agent 信号处理 个人博客 Claude AnythingLLM AnythingLLM安装 游戏引擎 uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 WSL2 上安装 Ubuntu seleium 算力 c/c++ 串口 vpn 串口驱动 CH341 uart 485 bcompare Beyond Compare 模拟器 教程 用户缓冲区 EMQX 通信协议 双系统 GRUB引导 Linux技巧 模拟实现 frp 支付 微信支付 开放平台 ArkTs ArkUI 基础环境 AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 VS Code fd 文件描述符 P2P HDLC 社交电子 uv Mac内存不够用怎么办 阻塞队列 生产者消费者模型 服务器崩坏原因 AD 域管理 cocoapods junit 游戏开发 SenseVoice 显示管理器 lightdm gdm lio-sam SLAM can 线程池 开发环境 嵌入式Linux IPC yolov8 wps banner USB网络共享 Kali 渗透 linux安装配置 昇腾 npu 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 版本 软负载 ssh漏洞 ssh9.9p2 CVE-2025-23419 minecraft 读写锁 rnn 中兴光猫 换光猫 网络桥接 自己换光猫 HTTP 服务器控制 ESP32 DeepSeek 备选 网站 调用 示例 银河麒麟桌面操作系统 Kylin OS AD域 迁移指南 毕昇JDK 分布式训练 语音识别 AutoDL ubuntu24.04.1 seatunnel IO模型 Ubuntu22.04 开发人员主页 vasp安装 trea idea 查询数据库服务IP地址 SQL Server 换源 Debian qt项目 qt项目实战 qt教程 rag ragflow 源码启动 EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 国标28181 视频监控 监控接入 语音广播 流程 SIP SDP k8s二次开发 集群管理 Radius UOS1070e Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 代码托管服务 perf 键盘 银河麒麟高级服务器 外接硬盘 Kylin DBeaver 数据仓库 kerberos 根服务器 clickhouse 华为机试 outlook DeepSeek r1 deekseek 产测工具框架 IMX6ULL 管理框架 做raid 装系统 Java Applet URL操作 服务器建立 Socket编程 网络文件读取 laravel OpenManus 小智AI服务端 xiaozhi TTS openstack Xen 宝塔 IMX317 MIPI H265 VCU composer miniapp 真机调试 调试 debug 断点 网络API请求调试方法 Node-Red 编程工具 流编程 CVE-2024-7347 内网服务器 内网代理 内网通信 VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 Logstash 日志采集 NLP模型 需求分析 规格说明书 自学笔记 小米 澎湃OS Android rustdesk 信号 互信 飞书 dns web3 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 远程服务 程序 性能分析 bat 单一职责原则 x64 SIGSEGV SSE xmm0 稳定性 看门狗 策略模式 单例模式 OpenSSH CDN 自动化任务管理 easyui spark HistoryServer Spark YARN jobhistory Headless Linux 大模型应用 安防软件 端口测试 相机 权限 asp.net大文件上传下载 yum源切换 更换国内yum源 AISphereButler IPv4 子网掩码 公网IP 私有IP 自定义客户端 SAS SSH 密钥生成 SSH 公钥 私钥 生成 程序员创富 zookeeper flink nfs 飞牛nas fnos 大大通 第三代半导体 碳化硅 TrueLicense 7z UDP的API使用 Windows ai工具 ldap armbian u-boot GIS 遥感 WebGIS Jellyfin vr 免费域名 域名解析 项目部署到linux服务器 项目部署过程 ftp keepalived sonoma 自动更新 xshell termius iterm2 超融合 neo4j 数据库开发 database h.264 LLM Web APP Streamlit 李心怡 glibc big data cpp-httplib MI300x 服务网格 istio Linux的权限 vscode1.86 1.86版本 ssh远程连接 AI-native Docker Desktop SRS 流媒体 直播 SSL证书 sysctl.conf vm.nr_hugepages zip unzip 软链接 硬链接 OpenHarmony db 鸿蒙开发 移动开发 MDK 嵌入式开发工具 论文笔记 sublime text 沙盒 网络爬虫 知识图谱 sequoiaDB HarmonyOS NEXT 原生鸿蒙 prometheus数据采集 prometheus数据模型 prometheus特点 计算生物学 生物信息 基因组 onlyoffice 云耀服务器 fstab kotlin 虚幻引擎 virtualbox Linux find grep 问题解决 ubuntu24 vivado24 top Linux top top命令详解 top命令重点 top常用参数 Sealos 论文阅读 本地化部署 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 ubuntu20.04 ros1 Noetic 20.04 apt 安装 在线office 欧标 OCPP WLAN chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 rancher 防火墙 NAT转发 NAT Server 烟花代码 烟花 元旦 性能调优 安全代理 玩机技巧 软件分享 软件图标 嵌入式系统开发 react native CentOS Stream CentOS MySql 网络建设与运维 网络搭建 神州数码 神州数码云平台 云平台 树莓派 VNC xpath定位元素 信创 信创终端 中科方德 PPI String Cytoscape CytoHubba Unity插件 iventoy VmWare OpenEuler css3 harmonyOS面试题 ip协议 环境配置 镜像 抗锯齿 ShenTong UDP 加解密 Yakit yaklang nlp 流量运营 金仓数据库 2025 征文 数据库平替用金仓 KylinV10 麒麟操作系统 Vmware 粘包问题 带外管理 MVS 海康威视相机 健康医疗 互联网医院 软件卸载 系统清理 proxy模式 Linux权限 权限命令 特殊权限 AI员工 llama.cpp 磁盘清理 navicat Python基础 Python教程 Python技巧 Attention 容器技术 docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 状态模式 QT 5.12.12 QT开发环境 Ubuntu18.04 Qwen2.5-VL vllm rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK PX4 MacOS 虚拟现实 对比 工具 meld DiffMerge jina 多路转接 项目部署 grub 版本升级 扩容 进程优先级 调度队列 进程切换 SEO hexo word nosql 大模型推理 gnu ssh远程登录 开发 deployment daemonset statefulset cronjob 浏览器开发 AI浏览器 AI Agent 字节智能运维 win服务器架设 windows server kernel 多端开发 智慧分发 应用生态 鸿蒙OS su sudo SVN Server tortoise svn 元服务 应用上架 端口聚合 windows11 HAProxy ABAP 钉钉 物联网开发 区块链 拓扑图 存储维护 NetApp存储 EMC存储 System V共享内存 进程通信 TCP协议 代理服务器 openvpn server openvpn配置教程 centos安装openvpn HiCar CarLife+ CarPlay QT RK3588 极限编程 milvus docker desktop image visual studio ranger MySQL8.0 autodl 端口 查看 ss 智能音箱 智能家居 docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 IMM 大模型部署 查看显卡进程 fuser ArtTS rpa fast wpf wsgiref Web 服务器网关接口 nvm whistle 云桌面 微软 AD域控 证书服务器 java-rocketmq ardunio BLE iperf3 带宽测试 浏览器自动化 抓包工具 Xinference 企业网络规划 华为eNSP 内网环境 网卡的名称修改 eth0 ens33 perl hosts vu大文件秒传跨域报错cors 合成模型 扩散模型 图像生成 docker部署Python yum换源 搭建个人相关服务器 电视剧收视率分析与可视化平台 React Next.js 开源框架 搜狗输入法 中文输入法 centos 7 数字证书 签署证书 yolov5 智能电视 ubuntu 18.04 dock 加速 服务器正确解析请求体 MAC SecureCRT IO 视频平台 录像 视频转发 视频流 开机黑屏 MobaXterm MAVROS 四旋翼无人机 达梦 DM8 安装MySQL 通信工程 毕业 Reactor反应堆 接口优化 风扇控制软件 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 conda配置 conda镜像源 热榜 mcp服务器 client close 离线部署dify 解决方案 macOS mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 js 西门子PLC 通讯 vnc