三、内存映射
10 内存映射
11 内存映射相关系统调用
/* #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); - 功能:将一个文件或者设备的数据映射到内存中 - 参数: - void *addr: NULL, 由内核指定 - length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。 获取文件的长度:stat lseek - prot : 对申请的内存映射区的操作权限 -PROT_EXEC :可执行的权限 -PROT_READ :读权限 -PROT_WRITE :写权限 -PROT_NONE :没有权限 要操作映射内存,必须要有读的权限。 PROT_READ、PROT_READ|PROT_WRITE - flags : - MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项 - MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write) - fd: 需要映射的那个文件的文件描述符 - 通过open得到,open的是一个磁盘文件 - 注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。 prot: PROT_READ open:只读/读写 prot: PROT_READ | PROT_WRITE open:读写 - offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不便宜。 - 返回值:返回创建的内存的首地址 失败返回MAP_FAILED,(void *) -1 int munmap(void *addr, size_t length); - 功能:释放内存映射 - 参数: - addr : 要释放的内存的首地址 - length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。 */ /* 使用内存映射实现进程间通信: 1.有关系的进程(父子进程) - 还没有子进程的时候 - 通过唯一的父进程,先创建内存映射区 - 有了内存映射区以后,创建子进程 - 父子进程共享创建的内存映射区 2.没有关系的进程间通信 - 准备一个大小不是0的磁盘文件 - 进程1 通过磁盘文件创建内存映射区 - 得到一个操作这块内存的指针 - 进程2 通过磁盘文件创建内存映射区 - 得到一个操作这块内存的指针 - 使用内存映射区通信 注意:内存映射区通信,是非阻塞。 */ #include <stdio.h> #include <sys/mman.h> #include <fcntl.h> #include <sys/types.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <wait.h> // 作业:使用内存映射实现没有关系的进程间的通信。 int main() { // 1.打开一个文件 int fd = open("test.txt", O_RDWR); int size = lseek(fd, 0, SEEK_END); // 获取文件的大小 // 2.创建内存映射区 void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(ptr == MAP_FAILED) { perror("mmap"); exit(0); } // 3.创建子进程 pid_t pid = fork(); if(pid > 0) { //父进程读数据 wait(NULL); //回收子进程 char buf[64]; strcpy(buf, (char *)ptr); printf("read data : %s\n", buf); } else if(pid == 0) { // 子进程写数据 strcpy((char *)ptr, "nihao a, son!!!"); } // 关闭内存映射区 munmap(ptr, size); return 0; }
内存映射完成文件拷贝
1.如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功? void * ptr = mmap(...); ptr++; 可以对其进行++操作 munmap(ptr, len); // 错误,要保存地址 2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样? 错误,返回MAP_FAILED open()函数中的权限建议和prot参数的权限保持一致。 3.如果文件偏移量为1000会怎样? 偏移量必须是4K的整数倍,返回MAP_FAILED 4.mmap什么情况下会调用失败? - 第二个参数:length = 0 - 第三个参数:prot - 只指定了写权限 - prot PROT_READ | PROT_WRITE 第5个参数fd 通过open函数时指定的 O_RDONLY / O_WRONLY 5.可以open的时候O_CREAT一个新文件来创建映射区吗? - 可以的,但是创建的文件的大小如果为0的话,肯定不行 - 可以对新的文件进行扩展 - lseek() - truncate() 6.mmap后关闭文件描述符,对mmap映射有没有影响? int fd = open("XXX"); mmap(,,,,fd,0); close(fd); 映射区还存在,创建映射区的fd被关闭,没有任何影响。 7.对ptr越界操作会怎样? void * ptr = mmap(NULL, 100,,,,,); 4K 越界操作操作的是非法的内存 -> 段错误
// 使用内存映射实现文件拷贝的功能 /* 思路: 1.对原始的文件进行内存映射 2.创建一个新文件(拓展该文件) 3.把新文件的数据映射到内存中 4.通过内存拷贝将第一个文件的内存数据拷贝到新的文件内存中 5.释放资源 */ #include <stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { // 1.对原始的文件进行内存映射 int fd = open("english.txt", O_RDWR); if(fd == -1) { perror("open"); exit(0); } // 获取原始文件的大小 int len = lseek(fd, 0, SEEK_END); // 2.创建一个新文件(拓展该文件) int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664); if(fd1 == -1) { perror("open"); exit(0); } // 对新创建的文件进行拓展 truncate("cpy.txt", len); write(fd1, " ", 1); // 3.分别做内存映射 void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); void * ptr1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0); if(ptr == MAP_FAILED) { perror("mmap"); exit(0); } if(ptr1 == MAP_FAILED) { perror("mmap"); exit(0); } // 内存拷贝 memcpy(ptr1, ptr, len); //第一个参数是往哪拷贝 第二个参数是从哪拷贝 // 释放资源 munmap(ptr1, len); munmap(ptr, len); close(fd1); close(fd); return 0; } //整体实现的功能是从copy.txt拷贝到cpy.txt
匿名映射
/* 匿名映射:不需要文件实体进程一个内存映射 可以用来父子间文件通信 */ #include <stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <sys/wait.h> int main() { // 1.创建匿名内存映射区 int len = 4096; void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if(ptr == MAP_FAILED) { perror("mmap"); exit(0); } // 父子进程间通信 pid_t pid = fork(); if(pid > 0) { // 父进程 strcpy((char *) ptr, "hello, world"); wait(NULL); }else if(pid == 0) { // 子进程 sleep(1); //先写再读 printf("%s\n", (char *)ptr); } // 释放内存映射区 int ret = munmap(ptr, len); if(ret == -1) { perror("munmap"); exit(0); } return 0; }
四、信号
01 信号的概念
02 linux信号
03 信号的五种默认处理动作
04 信号相关的函数
/* #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); - 功能:给任何的进程或者进程组pid, 发送任何的信号 sig - 参数: - pid : > 0 : 将信号发送给指定的进程 = 0 : 将信号发送给当前的进程组 = -1 : 将信号发送给每一个有权限接收这个信号的进程 < -1 : 这个pid=某个进程组的ID取反 (-12345) - sig : 需要发送的信号的编号或者是宏值,0表示不发送任何信号 kill(getppid(), 9); kill(getpid(), 9); int raise(int sig); - 功能:给当前进程发送信号 - 参数: - sig : 要发送的信号 - 返回值: - 成功 0 - 失败 非0 kill(getpid(), sig); void abort(void); - 功能: 发送SIGABRT信号给当前的进程,杀死当前进程 kill(getpid(), SIGABRT); */ #include <stdio.h> #include <sys/types.h> #include <signal.h> #include <unistd.h> int main() { pid_t pid = fork(); if(pid == 0) { // 子进程 int i = 0; for(i = 0; i < 5; i++) { printf("child process\n"); //子进程每隔一秒打印一句话 sleep(1); } } else if(pid > 0) { // 父进程 printf("parent process\n"); sleep(2); printf("kill child process now\n"); //父进程各两秒杀死子进程 kill(pid, SIGINT); //杀死子进程 } return 0; } //多进程抢夺CPU资源,不能确定父子谁先执行
alarm
/* #include <unistd.h> unsigned int alarm(unsigned int seconds); - 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候, 函数会给当前的进程发送一个信号:SIGALARM - 参数: seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。 取消一个定时器,通过alarm(0)。 - 返回值: - 之前没有定时器,返回0 - 之前有定时器,返回之前的定时器剩余的时间 - SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。 alarm(10); -> 返回0 过了1秒 alarm(5); -> 返回9 alarm(100) -> 该函数是不阻塞的 */ #include <stdio.h> #include <unistd.h> int main() { int seconds = alarm(5); //5秒终止程序 printf("seconds = %d\n", seconds); // 0 sleep(1); seconds = alarm(1); // 不阻塞 printf("seconds = %d\n", seconds); // 3 while(1) { } return 0; }
// 1秒钟电脑能数多少个数? #include <stdio.h> #include <unistd.h> /* 实际的时间 = 内核时间 + 用户时间 + 消耗的时间 进行文件IO操作的时候比较浪费时间 定时器,与进程的状态无关(自然定时法)。无论进程处于什么状态,alarm都会计时。 */ int main() { alarm(1); //一秒终止 int i = 0; while(1) { printf("%i\n", i++); } return 0; }
alarm返回值为还剩余多少时间
多次调用alarm只会有一个生效
下一个替代上一个
setitimer
/* #include <sys/time.h> int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); - 功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时 - 参数: - which : 定时器以什么时间计时 ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM 常用 ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF - new_value: 设置定时器的属性 struct itimerval { // 定时器的结构体 struct timeval it_interval; // 每个阶段的时间,间隔时间 struct timeval it_value; // 延迟多长时间执行定时器 }; struct timeval { // 时间的结构体 time_t tv_sec; // 秒数 suseconds_t tv_usec; // 微秒 }; 过10秒后,每个2秒定时一次 - old_value :记录上一次的定时的时间参数,一般不使用,指定NULL - 返回值: 成功 0 失败 -1 并设置错误号 */ #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; }
05 信号捕捉函数
signal
/* #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); - 功能:设置某个信号的捕捉行为 - 参数: - signum: 要捕捉的信号 - handler: 捕捉到信号要如何处理 - SIG_IGN : 忽略信号 - SIG_DFL : 使用信号默认的行为 - 回调函数 : 这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。 回调函数: - 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义 - 不是程序员调用,而是当信号产生,由内核调用 - 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。 - 返回值: 成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL 失败,返回SIG_ERR,设置错误号 SIGKILL SIGSTOP不能被捕捉,不能被忽略。 */ #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> void myalarm(int num) { printf("捕捉到了信号的编号是:%d\n", num); printf("xxxxxxx\n"); } // 过3秒以后,每隔2秒钟定时一次 int main() { // 注册信号捕捉 // signal(SIGALRM, SIG_IGN); // signal(SIGALRM, SIG_DFL); // void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。 signal(SIGALRM, myalarm); 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; }
sigaction
/* #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); - 功能:检查或者改变信号的处理。信号捕捉 - 参数: - signum : 需要捕捉的信号的编号或者宏值(信号的名称) - act :捕捉到信号之后的处理动作 - oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL - 返回值: 成功 0 失败 -1 struct sigaction { // 函数指针,指向的函数就是信号捕捉到之后的处理函数 void (*sa_handler)(int); // 不常用 void (*sa_sigaction)(int, siginfo_t *, void *); // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。 sigset_t sa_mask; // 使用哪一个信号处理对捕捉到的信号进行处理 // 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction int sa_flags; // 被废弃掉了 void (*sa_restorer)(void); }; */ #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> void myalarm(int num) { printf("捕捉到了信号的编号是:%d\n", num); printf("xxxxxxx\n"); } // 过3秒以后,每隔2秒钟定时一次 int main() { //给结构体初始化 struct sigaction act; //sigaction是结构体 act.sa_flags = 0; act.sa_handler = myalarm; sigemptyset(&act.sa_mask); // 清空临时阻塞信号集 // 注册信号捕捉 sigaction(SIGALRM, &act, NULL); 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(); while(1); return 0; }
06 信号集
07 阻塞信号集和未决信号集
08 信号集相关的函数
1.用户通过键盘 Ctrl + C, 产生2号信号SIGINT (信号被创建) 2.信号产生但是没有被处理 (未决) - 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集) - SIGINT信号状态被存储在第二个标志位上 - 这个标志位的值为0, 说明信号不是未决状态 - 这个标志位的值为1, 说明信号处于未决状态 3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较 - 阻塞信号集默认不阻塞任何的信号 - 如果想要阻塞某些信号需要用户调用系统的API 4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了 - 如果没有阻塞,这个信号就被处理 - 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
/* 以下信号集相关的函数都是对自定义的信号集进行操作。 int sigemptyset(sigset_t *set); - 功能:清空信号集中的数据,将信号集中的所有的标志位置为0 - 参数:set,传出参数,需要操作的信号集 - 返回值:成功返回0, 失败返回-1 int sigfillset(sigset_t *set); - 功能:将信号集中的所有的标志位置为1 - 参数:set,传出参数,需要操作的信号集 - 返回值:成功返回0, 失败返回-1 int sigaddset(sigset_t *set, int signum); - 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号 - 参数: - set:传出参数,需要操作的信号集 - signum:需要设置阻塞的那个信号 - 返回值:成功返回0, 失败返回-1 int sigdelset(sigset_t *set, int signum); - 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号 - 参数: - set:传出参数,需要操作的信号集 - signum:需要设置不阻塞的那个信号 - 返回值:成功返回0, 失败返回-1 int sigismember(const sigset_t *set, int signum); - 功能:判断某个信号是否阻塞 - 参数: - set:需要操作的信号集 - signum:需要判断的那个信号 - 返回值: 1 : signum被阻塞 0 : signum不阻塞 -1 : 失败 */ #include <signal.h> #include <stdio.h> int main() { // 创建一个信号集 sigset_t set; // 清空信号集的内容 sigemptyset(&set); // 判断 SIGINT 是否在信号集 set 里 int ret = sigismember(&set, SIGINT); if(ret == 0) { printf("SIGINT 不阻塞\n"); //.. } else if(ret == 1) { printf("SIGINT 阻塞\n"); } // 添加几个信号到信号集中 sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); // 判断SIGINT是否在信号集中 ret = sigismember(&set, SIGINT); if(ret == 0) { printf("SIGINT 不阻塞\n"); } else if(ret == 1) { printf("SIGINT 阻塞\n"); //.. } // 判断SIGQUIT是否在信号集中 ret = sigismember(&set, SIGQUIT); if(ret == 0) { printf("SIGQUIT 不阻塞\n"); } else if(ret == 1) { printf("SIGQUIT 阻塞\n"); //.. } // 从信号集中删除一个信号 sigdelset(&set, SIGQUIT); // 判断SIGQUIT是否在信号集中 ret = sigismember(&set, SIGQUIT); if(ret == 0) { printf("SIGQUIT 不阻塞\n"); //.. } else if(ret == 1) { printf("SIGQUIT 阻塞\n"); } return 0; }
上面改变的是自定义的信号集
sigprocmask
/* int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); - 功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换) - 参数: - how : 如何对内核阻塞信号集进行处理 SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变 假设内核中默认的阻塞信号集是mask, mask | set SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞 mask &= ~set SIG_SETMASK:覆盖内核中原来的值 - set :已经初始化好的用户自定义的信号集 - oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL - 返回值: 成功:0 失败:-1 设置错误号:EFAULT、EINVAL int sigpending(sigset_t *set); - 功能:获取内核中的未决信号集 - 参数:set,传出参数,保存的是内核中的未决信号集中的信息。 */ // 编写一个程序,把所有的常规信号(1-31)的未决状态打印到屏幕 // 设置某些信号是阻塞的,通过键盘产生这些信号 #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> 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); //1秒打印一次内核中前31个状态 if(num == 10) { // 解除阻塞 sigprocmask(SIG_UNBLOCK, &set, NULL); } } return 0; }
09 内核实现信号捕捉的过程
10 sigchld信号
子进程给父进程发送信号
/* SIGCHLD信号产生的3个条件: 1.子进程结束 2.子进程暂停了 3.子进程继续运行 都会给父进程发送该信号,父进程默认忽略该信号。 使用SIGCHLD信号解决僵尸进程的问题。 */ #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <sys/wait.h> 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; }
五、共享内存
01 共享内存
效率最高,无需要内核介入
02 共享内存使用步骤
共享内存相关的函数 #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); - 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。 新创建的内存段中的数据都会被初始化为0 - 参数: - key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。 一般使用16进制表示,非0值 - size: 共享内存的大小 - shmflg: 属性 - 访问权限 - 附加属性:创建/判断共享内存是不是存在 - 创建:IPC_CREAT - 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用 IPC_CREAT | IPC_EXCL | 0664 - 返回值: 失败:-1 并设置错误号 成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。 void *shmat(int shmid, const void *shmaddr, int shmflg); - 功能:和当前的进程进行关联 - 参数: - shmid : 共享内存的标识(ID),由shmget返回值获取 - shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定 - shmflg : 对共享内存的操作 - 读 : SHM_RDONLY, 必须要有读权限 - 读写: 0 - 返回值: 成功:返回共享内存的首(起始)地址。 失败(void *) -1 int shmdt(const void *shmaddr); - 功能:解除当前进程和共享内存的关联 - 参数: shmaddr:共享内存的首地址 - 返回值:成功 0, 失败 -1 int shmctl(int shmid, int cmd, struct shmid_ds *buf); - 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进行被销毁了对共享内存是没有任何影响。 - 参数: - shmid: 共享内存的ID - cmd : 要做的操作 - IPC_STAT : 获取共享内存的当前的状态 - IPC_SET : 设置共享内存的状态 - IPC_RMID: 标记共享内存被销毁 - buf:需要设置或者获取的共享内存的属性信息 - IPC_STAT : buf存储数据 - IPC_SET : buf中需要初始化数据,设置到内核中 - IPC_RMID : 没有用,NULL key_t ftok(const char *pathname, int proj_id); - 功能:根据指定的路径名,和int值,生成一个共享内存的key - 参数: - pathname:指定一个存在的路径 /home/nowcoder/Linux/a.txt / - proj_id: int类型的值,但是这系统调用只会使用其中的1个字节 范围 : 0-255 一般指定一个字符 'a' 问题1:操作系统如何知道一块共享内存被多少个进程关联? - 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch - shm_nattach 记录了关联的进程个数 问题2:可不可以对共享内存进行多次删除 shmctl - 可以的 - 因为shmctl 标记删除共享内存,不是直接删除 - 什么时候真正删除呢? 当和共享内存关联的进程数为0的时候,就真正被删除 - 当共享内存的key为0的时候,表示共享内存被标记删除了 如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。 共享内存和内存映射的区别 1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外) 2.共享内存效果更高 3.内存 所有的进程操作的是同一块共享内存。 内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。 4.数据安全 - 进程突然退出 共享内存还存在 内存映射区消失 - 运行进程的电脑死机,宕机了 数据存在在共享内存中,没有了 内存映射区的数据 ,由于磁盘文件中的数据还在,所以内存映射区的数据还存在。 5.生命周期 - 内存映射区:进程退出,内存映射区销毁 - 共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机 如果一个进程退出,会自动和共享内存进行取消关联。
03 共享内存操作函数
write_shm.c
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h> int main() { // 1.创建一个共享内存 int shmid = shmget(100, 4096, IPC_CREAT|0664); printf("shmid : %d\n", shmid); // 2.和当前进程进行关联 void * ptr = shmat(shmid, NULL, 0); //返回共享内存的首地址 char * str = "helloworld"; // 3.写数据 memcpy(ptr, str, strlen(str) + 1); printf("按任意键继续\n"); getchar(); // 4.解除关联 shmdt(ptr); // 5.删除共享内存 shmctl(shmid, IPC_RMID, NULL); return 0; }
read_shm.c
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h> int main() { // 1.获取一个共享内存 int shmid = shmget(100, 0, IPC_CREAT); //第一个参数要对上 第二个参数要小于4096 printf("shmid : %d\n", shmid); // 2.和当前进程进行关联 void * ptr = shmat(shmid, NULL, 0); // 3.读数据 printf("%s\n", (char *)ptr); printf("按任意键继续\n"); getchar(); // 4.解除关联 shmdt(ptr); // 5.删除共享内存 shmctl(shmid, IPC_RMID, NULL); return 0; }
要打开两个终端实现,一个写数据,一个读数据
04 共享内存操作命令