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

006_ipc概述及共享内存

【理论】

在Linux系统中,共享内存是一种进程间通信(IPC)机制,它允许多个进程访问同一块内存区域。这种机制可以提高进程间的数据交换速度,因为它们不需要通过其他通信方式(如管道或消息队列)来传递数据。

以下是Linux共享内存的一些关键概念:

  1. 共享内存段:共享内存的基本单位,是一个连续的内存区域,可以被多个进程映射到它们的地址空间。

  2. 键值:用于标识共享内存段的唯一键值。进程可以通过这个键值来创建、访问或删除共享内存段。

  3. 权限:共享内存段具有权限设置,包括读、写和执行权限,以控制不同进程对共享内存段的访问。

  4. 大小:共享内存段的大小可以根据需要设置,但通常受限于系统内存的大小。

  5. ** Attach/Detach**:进程可以通过shmat()系统调用将共享内存段附加到其地址空间,通过shmdt()系统调用将其分离。

  6. IPC对象:共享内存段是IPC对象的一种,其他IPC对象包括消息队列、信号量和管道。

  7. 持久性:共享内存段在创建它的进程结束后仍然存在,直到显式删除或系统重启。

  8. 同步:由于多个进程可以同时访问共享内存,因此需要适当的同步机制(如互斥锁)来避免竞态条件。

在Linux中,共享内存可以通过多种方式实现,包括:

  • POSIX共享内存:使用shm_open()ftruncate()mmap()等系统调用来创建和管理共享内存。
  • System V共享内存:使用shmget()shmat()shmdt()shmctl()等系统调用来创建和管理共享内存。

共享内存的使用可以提高多进程应用程序的性能,但同时也需要谨慎处理同步和错误处理,以确保数据的一致性和系统的稳定性。

顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc()分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量

System V共享内存

System V共享内存是一种传统的共享内存实现,它使用一个键值来标识共享内存段。以下是System V共享内存的常用函数:

shmget

shmget函数是用于创建或获取共享内存段的系统调用,以下是关于该函数的详细信息:

函数原型

#include <sys/ipc.h>
#include <sys/shm.h>

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

参数说明

  1. key_t key:共享内存的键值。可以是一个非0整数,也可以是IPC_PRIVATE。如果keyIPC_PRIVATE,则函数会创建一个新的共享内存段。如果key为0且shmflg中设置了IPC_PRIVATE标志,也会创建一个新的共享内存段。也可以这样获取唯一键值key_t key = ftok("shmfile", 65);
  2. size_t size:要创建的共享内存的大小,单位为字节。内存分配是以页为单位的,所以在某些系统中,即使只申请一个字节的内存,也会分配整整一页(例如,在i386机器中,一页的大小默认为4096字节)。
  3. int shmflg:标志位,用于指定权限和创建选项。可以包含以下标志:
    • IPC_CREAT:如果共享内存不存在,则创建它。
    • IPC_EXCL:与IPC_CREAT一起使用,如果共享内存已存在,则返回错误。
    • 0666:权限位,表示读、写和执行的权限。

补充说明:

第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由shmget函数返回的信号量标识符。

第二个参数,size以字节为单位指定需要共享的内存容量

第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

返回值

  • 成功:返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存操作。
  • 失败:返回-1,并设置errno以指示错误原因。

以下是一个简单的示例,展示如何使用shmget函数创建共享内存:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

int main() {
    key_t key = 1234; // 可以是任何非0整数
    size_t size = 4096; // 1页内存
    int shmflg = IPC_CREAT | 0666; // 创建共享内存,权限为读写

    int shmid = shmget(key, size, shmflg);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    printf("Shared memory created with ID: %d\n", shmid);
    return 0;
}

注意事项

  • 共享内存并未提供同步机制,因此需要使用其他机制(如信号量)来同步对共享内存的访问

shmat

shmat函数是用于将共享内存段附加到进程地址空间的系统调用。以下是关于该函数的详细信息:

函数原型

#include <sys/types.h>
#include <sys/shm.h>

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

参数说明

  1. int shmid:共享内存段的标识符,通常是通过shmget函数获取的。
  2. const void *shmaddr:指定共享内存段附加到进程地址空间的位置。如果设置为NULL,则系统会自动选择一个合适的地址。
  3. int shmflg:标志位,用于指定附加选项。可以包含以下标志:
    • SHM_RND:如果shmaddr不为NULL,则它必须是一个页面的整数倍。
    • SHM_RDONLY:以只读方式附加共享内存。

补充说明:

第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。

第一个参数,shmid是由shmget()函数返回的共享内存标识。

第二个参数,shmaddr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

第三个参数,shmflg是一组标志位,通常为0。

调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

返回值

  • 成功:返回共享内存段附加到进程地址空间的首地址。
  • 失败:返回(void *)-1,并设置errno以指示错误原因。

以下是一个简单的示例,展示如何使用shmat函数附加共享内存:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

int main() {
    key_t key = 1234; // 共享内存的键值
    size_t size = 4096; // 共享内存的大小
    int shmflg = IPC_CREAT | 0666; // 创建共享内存,权限为读写

    // 创建共享内存
    int shmid = shmget(key, size, shmflg);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    // 附加共享内存
    void *shmaddr = shmat(shmid, NULL, 0);
    if (shmaddr == (void *)-1) {
        perror("shmat");
        return 1;
    }

    printf("Shared memory attached at address: %p\n", shmaddr);
    // 在这里可以访问共享内存...

    // 分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        return 1;
    }

    return 0;
}

注意事项

  • 附加共享内存后,可以通过返回的地址访问共享内存。
  • 使用完毕后,应使用shmdt函数分离共享内存。
  • 如果多个进程附加同一个共享内存段,它们将看到相同的内存内容。

shmdt

shmdt函数是用于将共享内存段从进程地址空间分离的系统调用。以下是关于该函数的详细信息:

函数原型

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);

参数说明

  • const void *shmaddr:共享内存段附加到进程地址空间的首地址,通常是通过shmat函数获取的。

补充说明:

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。

参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1

返回值

  • 成功:返回0。
  • 失败:返回-1,并设置errno以指示错误原因。

以下是一个简单的示例,展示如何使用shmdt函数分离共享内存:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

int main() {
    key_t key = 1234; // 共享内存的键值
    size_t size = 4096; // 共享内存的大小
    int shmflg = IPC_CREAT | 0666; // 创建共享内存,权限为读写

    // 创建共享内存
    int shmid = shmget(key, size, shmflg);
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    // 附加共享内存
    void *shmaddr = shmat(shmid, NULL, 0);
    if (shmaddr == (void *)-1) {
        perror("shmat");
        return 1;
    }

    printf("Shared memory attached at address: %p\n", shmaddr);
    // 在这里可以访问共享内存...

    // 分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        return 1;
    }

    return 0;
}

注意事项

  • 分离共享内存后,进程将无法再通过返回的地址访问共享内存。
  • 如果共享内存段被多个进程附加,分离操作不会销毁共享内存段,直到所有附加的进程都分离了该共享内存段。
  • 分离共享内存不会释放共享内存段本身,需要使用shmctl函数的IPC_RMID操作来释放共享内存段。

shmctl

shmctl函数是用于对共享内存段进行控制操作(比如:删除)的系统调用。以下是关于该函数的详细信息:

函数原型

#include <sys/ipc.h>
#include <sys/shm.h>

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

参数说明

  • int shmid:共享内存段的标识符,通常是通过shmget函数获取的。
  • int cmd:要执行的控制命令,可以是以下之一:
    • IPC_STAT:获取共享内存段的状态信息,并将信息存储在buf指向的结构体中。
    • IPC_SET:设置共享内存段的状态信息,buf指向的结构体中包含要设置的信息。
    • IPC_RMID:标记共享内存段为删除,当最后一个附加该共享内存段的进程分离时,共享内存段将被销毁。
  • struct shmid_ds *buf:指向shmid_ds结构体的指针,用于存储或设置共享内存段的状态信息。

补充说明:

与信号量的semctl()函数一样,shmctl是用来控制共享内存的

第一个参数,shmid是shmget()函数返回的共享内存标识符。

第二个参数,cmd是要采取的操作,它可以取下面的三个值 :

  • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
  • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
  • IPC_RMID:删除共享内存段

第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

shmid_ds结构 至少包括以下成员:

1

2

3

4

5

6

struct shmid_ds

{

    uid_t shm_perm.uid;

    uid_t shm_perm.gid;

    mode_t shm_perm.mode;

};

返回值

  • 成功:返回0。
  • 失败:返回-1,并设置errno以指示错误原因。

shmid_ds结构体

shmid_ds结构体包含了共享内存段的状态信息,其定义如下:

struct shmid_ds {
    struct ipc_perm shm_perm;    /* Operation permission */
    size_t shm_segsz;            /* Size of segment in bytes */
    __kernel_time_t shm_atime;   /* Last attach time */
    __kernel_time_t shm_dtime;   /* Last detach time */
    __kernel_time_t shm_ctime;   /* Last change time */
    pid_t shm_cpid;              /* PID of creator */
    pid_t shm_lpid;              /* PID of last operator */
    shmatt_t shm_nattch;         /* Number of current attaches */
    __kernel_time_t shmLongrightarrow

以下是一个简单的示例,展示如何使用shmctl函数获取共享内存段的状态信息:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <stdio.h>

int main() {
    key_t key = 1234; // 共享内存的键值
    int shmid = shmget(key, 0, 0); // 获取共享内存段的标识符
    if (shmid == -1) {
        perror("shmget");
        return 1;
    }

    struct shmid_ds buf;
    if (shmctl(shmid, IPC_STAT, &buf) == -1) {
        perror("shmctl");
        return 1;
    }

    printf("Size of shared memory segment: %zu bytes\n", buf.shm_segsz);
    printf("Number of current attaches: %u\n", buf.shm_nattch);
    printf("PID of creator: %d\n", buf.shm_cpid);
    printf("PID of last operator: %d\n", buf.shm_lpid);

    return 0;
}

注意事项

  • IPC_STAT命令用于获取共享内存段的状态信息,buf指向的结构体将填充这些信息。
  • IPC_SET命令用于设置共享内存段的状态信息,buf指向的结构体应包含要设置的信息。
  • IPC_RMID命令用于标记共享内存段为删除,当最后一个附加该共享内存段的进程分离时,共享内存段将被销毁。
  • shmctl函数不会分离或附加共享内存段,它只是用于控制共享内存段的状态。

以上函数解释中的补充说明部分,是参考如下链接的,还有testcode01也是参考下面链接的,还请查看原链接比较详细:Linux进程间通信(六):共享内存 shmget()、shmat()、shmdt()、shmctl() - 52php - 博客园

testcode_00

System V 共享内存是 Unix 系统中提供的一种进程间通信(IPC)机制,允许不同进程访问同一块内存区域。以下是一个使用 System V 共享内存的简单示例,我们将创建一个共享内存段,并在两个进程中对其进行读写操作。

这个例子将在 C 语言中实现,包括以下步骤:

  1. 创建共享内存段。
  2. 将共享内存段附加到当前进程的地址空间。
  3. 在一个进程中写入数据到共享内存。
  4. 在另一个进程中从共享内存读取数据。
  5. 从当前进程的地址空间分离共享内存段。
  6. 删除共享内存段。

下面是示例代码:

​
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include "./head/log.h"
#include <stdlib.h>
#include <fcntl.h>//open  creat  fcntl
#include <sys/ipc.h> //进程创建头文件
#include <sys/shm.h> //共享内存头文件
#include <sys/sem.h>
#include <sys/types.h>
#include <stdlib.h>
#include <pthread.h>//线程头文件
#include <semaphore.h>//信号量头文件


/*共享内存_TEST*/

#define SHM_SIZE 1024 //定义共享内存的大小

//systemV接口验证
int shm_test()
{
	key_t key;
	int shmid;
	char *data;
	int mode;

/*1.生成一个唯一的键值,用来创建共享内存使用*/
	key = ftok("shmfile", 65);
/*2.创建共享内存段*/
	shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT);
/*3.将共享内存段附加到当前进程的地址空间*/
	data = (char*) shmat(shmid, (void*)0, 0);
	if (data == (char*)(-1))
	{
		perror("shmat is ERROR!");
		exit(1);
	}
/*4.在共享内存中写入数据:在一个进程中写入数据到共享内存*/
	sprintf(data, "Hello, shared memory!");
/*创建子进程*/
	pid_t pid = fork();

	if(pid < 0)
	{
		//fork失败
		perror("fork is ERROR!");
		exit(1);
	}
	else if(pid == 0)
	{
		//子进程
		sleep(1);//确保父进程先运行
		printf("Child process: %s\n", data);//5.读取数据
		//从当前进程的地址空间分离共享内存
		shmdt(data);
	}
	else
	{
		//父进程
		printf("Parent process: %s\n", data);//5.读取数据
		//等待子进程结束
		wait(NULL);
		//从当前进程的地址空间分离共享内存
		shmdt(data);
	}

	return SUCCESS;
}

int main()
{
	LOGS("========================zll_debug_start===========================");
	shm_test();
	LOGS("========================zll_debug_end=============================");

	return SUCCESS;
}

​

运行结果如下:

请注意,在实际的生产环境中,你需要更详细地处理错误情况,并且可能需要使用信号量来同步对共享内存的访问,以避免竞态条件此外,共享内存的使用需要谨慎,因为不当的使用可能导致内存泄漏和数据不一致

testcode_01

结构数据:

shmread.c 实现如下:

#include <stddef.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "./head/log.h"


int main()
{
	void *shm = NULL;
	struct shared_use_st *shared; //指向shm
	int shmid; //共享内存标识符

	//1.创建共享内存
	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
	if(shmid == -1)
	{
		fprintf(stderr, "shmget failed!\n");
		exit(EXIT_FAILURE);
	}
	//2.将共享内存链接到当前进程的地址空间
	shm = shmat(shmid, 0, 0);
	if(shm == (void *)-1)
	{
		fprintf(stderr, "shmat failed!\n");
		exit(EXIT_FAILURE);
	}
	printf("\n Memory attached at %X\n", (int)shm);

	//3.设置共享内存
	//注意:shm有点类似通过malloc获取到的内存,所以这里需要做个类型强制转换
	shared = (struct shared_use_st*)shm;
	shared->written = 0;
	while(1)//读取共享内存中的数据
	{
		//没有进程向内存写数据,有数据可读取
		if(shared->written != 0)
		{
			printf("You wrote is: %s\n", shared->text);
			sleep(1);
			//读取完数据,设置written使共享内存段可写
			shared->written = 0;
			//输入end, 退出循环
			if(strncmp(shared->text, "end", 3) == 0)
			{
				break;
			}
		}
		else//有其他进程在写数据, 不能读取数据
		{
			sleep(1);
		}
	}
	//把共享内存从当前进程中分离
	if(shmdt(shm) == -1)
	{
		fprintf(stderr, "shmdt failed!\n");
		exit(EXIT_FAILURE);
	}
	//删除共享内存
	if(shmctl(shmid, IPC_RMID, 0) == -1)
	{
		fprintf(stderr, "shmctl(IPC_RMID) failed\n");
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}

程序shmread创建共享内存,然后将它连接到自己的地址空间。在共享内存的开始处使用了一个结构struct_use_st。该结构中有个标志written,当共享内存中有其他进程向它写入数据时,共享内存中的written被设置为0,程序等待。当它不为0时,表示没有进程对共享内存写入数据,程序就从共享内存中读取数据并输出,然后重置设置共享内存中的written为0,即让其可被shmwrite进程写入数据

shmwrite.c 实现如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include "./head/log.h"


int main()
{
	void *shm = NULL;
	struct shared_use_st *shared = NULL;
	char buffer[BUFSIZ + 1]; //用于保存输入的文本内容
	int shmid;

	//1.创建共享内存
	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
	if(shmid == -1)
	{
		fprintf(stderr, "shmget failed!\n");
		exit(EXIT_FAILURE);
	}

	//2.将共享内存链接到当前的进程地址空间
	shm = shmat(shmid, (void *)0, 0);
	if(shm == (void *)-1)
	{
		fprintf(stderr, "shmat failed!\n");
		exit(EXIT_FAILURE);
	}

	printf("Write Memory attched at %X\n", (int)shm);

	//3.设置共享内存
	shared = (struct shared_use_st *)shm;
	while(1)//4.向共享内存中写数据
	{
		//数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
		while(shared->written != 0)
		{
			sleep(1);
			printf("Write waiting...\n");
		}

		//向共享内存中写入数据
		printf("Enter some text: \n");
		fgets(buffer, BUFSIZ, stdin);
		strncpy(shared->text, buffer, TEXT_SZ);

		//写完数据,设置written使共享内存段可读
		shared->written = 1;

		//输入了end,退出循环
		if(strncmp(buffer, "end", 3) == 0)
		{
			break;
		}
	}
	//5.把共享内存从当前进程中分离
	if(shmdt(shm) == -1)
	{
		fprintf(stderr, "shmdt failed!\n");
		exit(EXIT_FAILURE);
	}
	sleep(2);
	exit(EXIT_FAILURE);
}

程序shmwrite取得共享内存并连接到自己的地址空间中。检查共享内存中的written,是否为0,若不是,表示共享内存中的数据还没有被完,则等待其他进程读取完成,并提示用户等待。若共享内存的written为0,表示没有其他进程对共享内存进行读取,则提示用户输入文本,并再次设置共享内存中的written为1,表示写完成,其他进程可对共享内存进行读操作

结果如下:

注意点:

1.关于shmget函数中的第一个参数键值key,两个进程的一定要保持一致,如本例中都是1234,这个一旦不一致的话,就会导致共享内存标识符不一致,最后导致两个进程无法进行通讯

2.优点:我们可以看到使用共享内存进行进程间的通信真的是非常方便,而且函数的接口也简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,也加快了程序的效率。同时,它也不像匿名管道那样要求通信的进程有一定的父子关系。

3.缺点:共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。

4.要想让程序安全地执行,就要有一种进程同步的进制,保证在进入临界区的操作是原子操作。例如,可以使用前面所讲的信号量来进行进程的同步。因为信号量的操作都是原子性的。

POSIX共享内存

POSIX共享内存是一种基于文件的共享内存实现,它使用文件系统中的文件来标识共享内存段。以下是POSIX共享内存的常用函数:

shm_open

shm_open 是 POSIX API 中的一个函数,用于创建或打开一个 POSIX 共享内存对象。这个函数的行为类似于标准的 open 函数,但是它是专门用于共享内存的

函数原型

#include <sys/mman.h>
#include <fcntl.h>

int shm_open(const char *name, int oflag, mode_t mode);

参数说明:

  • name: 共享内存对象的名称。这个名称应该是一个以 / 开头的字符串,以避免与现有的文件系统路径冲突。
  • oflag: 打开标志,可以是以下值的组合:
    • O_RDONLY: 只读打开
    • O_RDWR: 读写打开
    • O_CREAT: 如果共享内存对象不存在,则创建它
    • O_EXCL: 与 O_CREAT 一起使用,确保创建的是一个新的共享内存对象,如果对象已存在,则返回错误
    • O_TRUNC: 如果共享内存对象已存在,则将其大小截断为0
  • mode: 当创建共享内存对象时,用于设置权限位。这只有在 O_CREAT 标志被设置时才有意义。权限位由 mode_t 类型表示,通常是一个八进制数,例如 0644

返回值

  • 成功时,返回一个非负的文件描述符,用于后续的 mmap 调用。
  • 失败时,返回 -1 并设置 errno 来指示错误。

示例:

#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    const char *name = "/my_shm";
    int shm_fd;

    // 创建或打开共享内存对象
    shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        exit(EXIT_FAILURE);
    }

    // ... 使用共享内存 ...

    // 关闭文件描述符
    close(shm_fd);

    // 删除共享内存对象
    shm_unlink(name);

    return 0;
}

在使用 shm_open 创建共享内存对象后,通常需要使用 ftruncate 来设置共享内存的大小,然后使用 mmap 将其映射到进程的地址空间。在使用完毕后,应该使用 munmap 解除映射,并通过 close 关闭文件描述符,最后使用 shm_unlink 删除共享内存对象。

ftruncate

ftruncate 是一个 POSIX 系统调用,用于调整一个已经打开的文件的大小。如果新的文件大小小于原来的大小,则文件将被截断;如果新的文件大小大于原来的大小,则文件将被扩展,并且扩展部分的内容通常是未定义的(通常是零)

函数原型

#include <unistd.h>
#include <sys/types.h>

int ftruncate(int fd, off_t length);

参数说明

  • fd: 文件描述符,指向要调整大小的文件。
  • length: 新的文件长度。如果 length 小于文件的当前大小,文件将被截断;如果 length 大于当前大小,文件将被扩展。

返回值

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

示例

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

int main() {
    const char *filename = "example.txt";
    int fd;
    off_t new_size = 1024; // 设置新文件大小为 1024 字节

    // 打开文件
    fd = open(filename, O_RDWR | O_CREAT, 0666);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 调整文件大小
    if (ftruncate(fd, new_size) == -1) {
        perror("ftruncate");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 关闭文件描述符
    close(fd);

    return 0;
}

在上面的示例中,我们打开了一个文件,并通过 ftruncate 将其大小调整为 1024 字节。如果文件之前小于这个大小,它将被扩展;如果大于这个大小,将被截断。

ftruncate 也可以用于调整 POSIX 共享内存对象的大小。在 shm_open 创建共享内存对象后,通常需要使用 ftruncate 来设置共享内存的大小,然后才能使用 mmap 将其映射到进程的地址空间。

mmap

mmap(Memory Map)是Linux和类Unix操作系统中用于将文件或设备映射到进程的地址空间的一个系统调用。通过mmap,文件的内容可以在内存中直接访问,而不需要使用readwrite系统调用。这对于性能密集型应用程序(如数据库和视频处理软件)非常有用,因为它可以减少数据复制操作,并允许更高效的内存访问

函数原型

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);

参数说明

  • addr: 指定映射区域的起始地址。通常设置为NULL,让内核自动选择地址。
  • length: 要映射的区域的长度。
  • prot: 提供对映射区域的保护。可能的值包括:
    • PROT_EXEC: 页可以被执行。
    • PROT_READ: 页可以被读取。
    • PROT_WRITE: 页可以被写入。
    • PROT_NONE: 页不能被访问。
  • flags: 影响映射区域的特性。可能的值包括:
    • MAP_SHARED: 对映射区域的写操作将对共享该映射的所有进程可见。
    • MAP_PRIVATE: 对映射区域的写操作会产生映射的拷贝(写时复制)。
    • MAP_FIXED: 将映射区域固定在addr指定的地址上。
    • 其他标志可以与这些基本标志组合使用。
  • fd: 文件描述符,表示要映射的文件。
  • offset: 文件内的偏移量,映射应该从文件的哪个位置开始。

返回值

  • 成功时,返回映射区域的地址。
  • 失败时,返回MAP_FAILED(通常是(void *) -1),并设置errno以指示错误。

示例

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    const char *filepath = "example.txt";
    int fd;
    struct stat sb;
    void *map;

    // 打开文件
    fd = open(filepath, O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 获取文件大小
    if (fstat(fd, &sb) == -1) {
        perror("fstat");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 将文件映射到内存
    map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 使用映射的内存(例如,打印文件内容)
    printf("%.*s\n", (int)sb.st_size, (char *)map);

    // 解除映射
    if (munmap(map, sb.st_size) == -1) {
        perror("munmap");
    }

    // 关闭文件描述符
    close(fd);

    return 0;
}

在这个例子中,我们打开了一个文件,使用fstat获取其大小,然后使用mmap将其映射到内存。之后,我们可以直接访问映射的内存来读取文件内容。最后,我们使用munmap来解除映射,并关闭文件描述符。注意,在映射文件时,通常不需要读取或写入整个文件,因为文件的内容已经直接映射到了内存中。

补充说明

fstat

fstat 是一个在 POSIX 兼容的操作系统(如 Linux、Unix 和 macOS)中使用的系统调用,它用于获取与一个已打开文件相关的信息。这个信息存储在一个 stat 结构体中,该结构体包含了文件的元数据,如文件大小、创建时间、最后修改时间、文件权限等。

函数原型

#include <sys/stat.h>
#include <unistd.h>

int fstat(int fd, struct stat *buf);

参数说明

  • fd: 文件描述符,它是一个非负整数,代表已打开的文件。
  • buf: 指向 stat 结构体的指针,fstat 会将文件的状态信息填充到这个结构体中。

返回值

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

示例

#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    int fd;
    struct stat sb;

    // 打开文件
    fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 使用 fstat 获取文件状态信息
    if (fstat(fd, &sb) == -1) {
        perror("fstat");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 打印文件大小
    printf("File size: %lld bytes\n", (long long) sb.st_size);

    // 关闭文件描述符
    close(fd);

    return 0;
}

在上面的示例中,我们打开了一个名为 “example.txt” 的文件,并使用 fstat 获取了它的状态信息。然后,我们从 stat 结构体中提取了文件大小并打印出来。

stat 结构体在不同的操作系统和架构中可能有不同的定义,但它通常包含以下字段:

  • st_dev: 文件所在设备的 ID。
  • st_ino: 文件的 inode 号。
  • st_mode: 文件的类型和权限。
  • st_nlink: 文件的硬链接数。
  • st_uid: 文件所有者的用户 ID。
  • st_gid: 文件所有者的组 ID。
  • st_rdev: 设备文件的设备 ID(如果文件不是设备文件,则为 0)。
  • st_size: 普通文件的总大小,以字节为单位。
  • st_blksize: 为文件 I/O 优化的块大小。
  • st_blocks: 分配给文件的 512 字节块的数量。
  • st_atime: 文件最后访问时间。
  • st_mtime: 文件内容最后修改时间。
  • st_ctime: 文件状态最后更改时间。

使用 fstat 可以获取这些信息,并据此进行相应的操作

shm_ptr

shmptr 并不是一个标准的系统调用或者函数名,但在一些上下文中,它可能是一个变量名或者宏,用来指向通过共享内存(shared memory)机制映射到进程地址空间的一段内存

在 POSIX 系统中,共享内存可以通过 shmgetshmat 和 shmdt 等函数来使用。以下是如何使用这些函数的一个基本示例:

  1. shmget - 获取共享内存标识符
  2. shmat - 将共享内存段映射到进程的地址空间
  3. shmdt - 从进程的地址空间分离共享内存段

示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

#define SHM_SIZE 1024  // 定义共享内存的大小

int main() {
    key_t key = 1234;  // 共享内存的键值
    int shmid;         // 共享内存标识符
    char *shmptr;      // 指向共享内存的指针

    // 创建共享内存段
    shmid = shmget(key, SHM_SIZE, 0644 | IPC_CREAT);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存映射到进程的地址空间
    shmptr = shmat(shmid, (void *)0, 0);
    if (shmptr == (char *)(-1)) {
        perror("shmat");
        exit(1);
    }

    // 使用共享内存
    sprintf(shmptr, "Hello, shared memory!");

    // 输出共享内存中的内容
    printf("%s\n", shmptr);

    // 分离共享内存段
    if (shmdt(shmptr) == -1) {
        perror("shmdt");
        exit(1);
    }

    // 删除共享内存段
    if (shmctl(shmid, IPC_RMID, 0) == -1) {
        perror("shmctl");
        exit(1);
    }

    return 0;
}

在这个例子中,shmptr 是一个指针,它指向通过 shmat 映射到进程地址空间的共享内存。我们使用 sprintf 将一个字符串写入共享内存,然后使用 printf 打印出来。在使用完共享内存后,我们使用 shmdt 将其从地址空间分离,并使用 shmctl 删除共享内存段。

请注意,共享内存是进程间通信(IPC)的一种机制,因此通常需要多个进程来演示其用法。上面的例子仅展示了如何在单个进程中创建和使用共享内存。在实际应用中,通常会有一个或多个生产者进程写入共享内存,以及一个或多个消费者进程从共享内存读取数据。

munmap

munmap 是 POSIX 系统调用之一,用于解除映射内存区域。当你使用 mmap 系统调用将文件或设备映射到进程的地址空间后,munmap 可以用来删除这些映射,并释放相关联的资源

函数原型

#include <sys/mman.h>

int munmap(void *addr, size_t length);

参数说明

  • addr:指向要解除映射的内存区域的指针。这个地址必须是页大小的整数倍。
  • length:指定要解除映射的内存区域的大小。

返回值

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

示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;
    void *ptr;
    struct stat sb;

    // 打开文件
    fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 获取文件大小
    if (fstat(fd, &sb) == -1) {
        perror("fstat");
        exit(EXIT_FAILURE);
    }

    // 将文件映射到内存
    ptr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    // 关闭文件描述符,因为映射已经建立
    close(fd);

    // 使用映射的内存...

    // 解除映射
    if (munmap(ptr, sb.st_size) == -1) {
        perror("munmap");
        exit(EXIT_FAILURE);
    }

    return 0;
}

在这个例子中,我们首先打开一个文件,然后使用 mmap 将其内容映射到内存。在使用完映射的内存后,我们调用 munmap 来解除映射。注意,在映射建立后,可以关闭文件描述符,因为映射会保持直到调用 munmap。如果程序在解除映射前退出,操作系统通常会自动清理映射。

shm_unlink

shm_unlink 是 POSIX 共享内存 API 的一部分,用于删除一个 POSIX 共享内存对象。共享内存对象是存在于文件系统中的一个特殊类型的文件,通常位于 /dev/shm/ 目录下,但它不占用磁盘空间,而是直接在内存中。

当你创建一个共享内存对象时,可以使用 shm_open 系统调用,而 shm_unlink 用于删除这个对象。删除共享内存对象会释放与其关联的资源,但需要注意的是,只有当所有打开该对象的进程都关闭了它之后,共享内存对象才会真正被删除。

函数原型

#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */

int shm_unlink(const char *name);

参数说明

  • name:共享内存对象的路径名。

返回值

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

示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    const char *name = "/my_shm"; // 共享内存对象的路径名
    int shm_fd;

    // 创建或打开共享内存对象
    shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        exit(EXIT_FAILURE);
    }

    // 设置共享内存对象的大小(可选)
    if (ftruncate(shm_fd, 4096) == -1) {
        perror("ftruncate");
        exit(EXIT_FAILURE);
    }

    // 使用共享内存...

    // 关闭共享内存对象
    if (close(shm_fd) == -1) {
        perror("close");
        exit(EXIT_FAILURE);
    }

    // 删除共享内存对象
    if (shm_unlink(name) == -1) {
        perror("shm_unlink");
        exit(EXIT_FAILURE);
    }

    return 0;
}

在这个例子中,我们首先使用 shm_open 创建或打开一个共享内存对象,然后设置其大小,接着可以在这个共享内存上执行所需的操作。最后,我们关闭了共享内存对象,并使用 shm_unlink 删除了它。

请注意,shm_unlink 调用不会立即删除共享内存对象,只有当所有打开该对象的进程都执行了 close 调用之后,对象才会被删除。如果其他进程还在使用该共享内存对象,那么 shm_unlink 只是从文件系统中删除了对象的引用,对象本身会保留直到最后一个进程关闭它。

testcode_02

以下举例是父子进程间通讯,如下code:

#include <stdio.h>
#include <sys/mman.h>//mmap接口
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include "./head/log.h"
#include <stdlib.h>
#include <fcntl.h>//open  creat  fcntl
#include <sys/ipc.h> //进程创建头文件
#include <sys/shm.h> //共享内存头文件
#include <sys/sem.h>
#include <sys/types.h>
#include <stdlib.h>
#include <pthread.h>//线程头文件
#include <semaphore.h>//信号量头文件

//共享内存POSIX接口验证
void shm_posix_test()
{
//#define SHM_NAME "/my_shm" //共享内存对象名称
//#define SHM_SIZES 4096      //共享内存大小
	const int SHM_SIZES = 4096;
	const char* SHM_NAME = "/my_shm";
	int shm_fd;
	void* shm_ptr;
	pid_t pid;

	pid = fork();
if(pid < 0)
{
	perror("fork is ERROR!");
	exit(1);
}
else if(pid == 0)//创建共享内存段
{
	LOGS("This is Son process......");
//1.创建或者打开共享内存对象
	shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
	if(shm_fd == -1)
	{
		perror("shm_open is ERROR!");
		exit(1);
	}
//2.设置共享内存的大小
	if(ftruncate(shm_fd, SHM_SIZES) == -1)
	{
		perror("ftruncate is ERROR!");
		exit(1);
	}
//3.映射共享内存到进程的地址空间
	shm_ptr = mmap(NULL, SHM_SIZES, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
	if(shm_ptr == MAP_FAILED)
	{
		perror("mmap is ERROR!");
		exit(1);
	}
//4.使用共享内存进行操作...
//例如:写入数据
	sprintf((char*)shm_ptr, "Hello, shared memory Posix......");
//5.以下是清理代码
//取消映射共享内存
	if(munmap(shm_ptr, SHM_SIZES) == -1)
	{
		perror("munmap is ERROR!");
		exit(1);
	}
//6.关闭共享内存描述符
	if(close(shm_fd == -1))
	{
		perror("close is ERROR!");
		exit(1);
	}
}
else//访问共享内存段
{
	 LOGS("This is Parents process......");
//1.打开共享内存对象
        shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
        if(shm_fd == -1)
        {
                perror("shm_open is ERROR!");
                exit(1);
        }
/*2.设置共享内存的大小
        if(ftruncate(shm_fd, SHM_SIZE) == -1)
        {
                perror("ftruncate is ERROR!");
                exit(1);
        }*/
//3.映射共享内存到进程的地址空间
        shm_ptr = mmap(NULL, SHM_SIZES, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
        if(shm_ptr == MAP_FAILED)
        {
                perror("mmap is ERROR!");
                exit(1);
        }
//4.使用共享内存进行操作...
//例如:写入数据
        printf("%s\n", (char*)shm_ptr);
//5.以下是清理代码
//取消映射共享内存
        if(munmap(shm_ptr, SHM_SIZES) == -1)
        {
                perror("munmap is ERROR!");
                exit(1);
        }
//6.关闭共享内存描述符
        if(close(shm_fd == -1))
        {
                perror("close is ERROR!");
                exit(1);
        }
}
}
int main()
{
	LOGS("========================zll_debug_start===========================");
	//shm_test();
	shm_posix_test();
	LOGS("========================zll_debug_end=============================");

	return SUCCESS;
}

在调试的过程中遇到以下两个bebug:

这个错是因为用了宏定义,如下用const char*就可以了

这个错误提示权限不够,经debug后,发现是因为父进程中的shm_open只给了读权限,像这种父子进程之间必须是读写权限都有的,因为相互之间存在读写关系,一旦权限不够就会出现问题,所以一定要保持一致,对于文件的操作权限,修改如下:

上面这段代码功能很清晰,就是在子进程中对共享内存写入字符串“Hello, shared memry Posix......”然后,从父进程中再读出这个内容,打印到终端,如下:

【testcode_03】

下面我们再举例一个无血缘关系的两个进程间的通讯方法,我们实现一个写端写入学生id号和名字的任务,读端读取这些内容,具体代码实现如下:

写段

#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>



typedef struct str_student{

	int idx;
	char name[50];
}Student;

int main()
{
	int fd, num, length;

	fd  = open("apx", O_RDWR | O_TRUNC | O_CREAT, 0666);
	length = sizeof(Student);
	ftruncate(fd, length);
	Student* std = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if(std == MAP_FAILED)
	{
		perror("mmap is failed!");
		exit(-1);
	}
	while(1)
	{
		std->idx = num;
		sprintf(std->name, "xiaonanana-%03d", num++);
		sleep(1);
	}
	munmap(std, length);
	close(fd);

}

读端

#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>


typedef struct str_student{
	int idx;
	char name[50];
}Student;

int main()
{
	int fd, length;

	fd = open("apx", O_RDWR);
	length = sizeof(Student);
	Student* std = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if(std == MAP_FAILED)
	{
		perror("mmap");
		exit(-1);
	}
	while(1)
	{
		printf("idx = %03d, name = %s\n", std->idx, std->name);
		sleep(1);
	}
}

注意,因为我们需要运行两个不同的进程,所以需要单独的编译出来两个可执行文件,所以就分开编译,指令:gcc -o shmposix_r.exe shmposix.c -lm

在编译C或C++程序时,-lm 选项用于链接数学库(libm),这个库包含了数学函数的实现,如 sin()cos()sqrt() 等。当你需要在程序中使用这些数学函数时,需要在编译命令中包含 -lm 选项。

  • gcc 或 g++:编译器命令。
  • math_example.c 或 math_example.cpp:源文件名。
  • -o math_example:指定输出文件名(可执行文件)。
  • -lm:告诉编译器链接数学库。
  • -lm 选项应该放在编译命令的最后,因为链接器是按照从左到右的顺序处理库的,确保所有需要数学库的代码都已经编译完成。
  • 如果你的程序中使用了其他库,而这些库又依赖于数学库,那么也需要在链接时包含 -lm
  • 在某些系统上,数学库可能默认就被链接了,但是为了可移植性和明确性,最好总是显式地包含 -lm

除了数学库,还有其他常用的库和对应的编译选项:

  • -lpthread:链接线程库,用于多线程编程。
  • -ldl:链接动态加载库,用于动态加载共享库。
  • -lrt:链接实时库,用于实时编程(在某些系统上)。

以下是运行结果:

注意先用运行shmposix_w.exe,再去运行shmposix_r.exe,效果如下

运行1和2后,读端就一直有数据输出,3和4是我输入ctrl+z停止进程操作


http://www.kler.cn/a/450011.html

相关文章:

  • ajax中get和post的区别,datatype返回的数据类型有哪些?web开发中数据提交的几种方式,有什么区别。
  • Spring Boot 中的 @Scheduled 定时任务以及开关控制
  • .NET Core 中使用 C# 获取Windows 和 Linux 环境兼容路径合并
  • LAUNCHXL_F28379D_Workspace_CCS124
  • PingCAP TiDB数据库专员PCTA认证笔记
  • 练14:DFS基础
  • Linux 下SVN新手操作手册
  • 解析mysqlbinlog
  • Word使用分隔符实现页面部分分栏
  • Kotlin - 协程结构化并发Structured Concurrency
  • CSS|13 position属性
  • [c++11(二)]Lambda表达式和Function包装器及bind函数
  • 数据结构---------二叉树前序遍历中序遍历后序遍历
  • MyBatis执行完sql后,返回的数值代表的意思
  • 基于PX4的多无人机集群中的的配置
  • 【软考高级】系统架构设计师复习笔记-精华版
  • 【C语言】判断回文
  • #error: WinSock.h has already been included解决方案
  • 解决PotPlayer无法播放S/W HEVC(H265)解码的视频
  • JVM 常用的参数说明与配置指南
  • 信管通低代码信息管理系统应用平台
  • Kafka 常见问题
  • Vue 前端代码规范
  • 游戏网站大全
  • 2.4 网络概念(分层、TCP)
  • 探索 Python编程 调试案例:配置日志记录器查看程序运行bug