1.进程入门
目录
捕捉一个指定的信号,且可以通过扩展响应函数来获取信号携带的额外数据
1.1进程概念

1.2进程的组织方式
进程起始是init,有了init才会有其他许多个父进程,子进程。
1.2.1pstree 查看进程间的关系
1.2.2查看每个进程的ID
ps -ef | more
UID是该进程由那个用户创建
PID是对应进程的ID
PPID是该进程父进程的ID
TTY:与其关联的终端 ?表示没有、tty1表示该进程会随着TTY1的退出而退出
2.进程的生老病死
2.1进程的状态
就绪态、执行态、睡眠态、暂停态、僵尸态、死亡态
这个状态的转换和RTOS很像,创建即就绪,延时即睡眠,暂停即停止,唤醒即执行,死亡再收尸,就绪大哥先走。这里说一下留下尸体。为什么进程死掉了还要把尸体留下?答:在进程退出的时候,将其退出的信息都封存在了尸体里,如果是正常退出,退出值是多少?如果被信号杀死?是哪个信号?这些信号是父进程最为关心的,因为父进程创建子进程就是为了完成某一项工作!
2.2父进程收尸
父进程调用wait()/waitpid()来查看孩子的“死亡信息”,顺便将孩子的状态置为EXIT_DEAD,即死亡态,只有处于这个状态的进程才能被系统回收。如何让父进程及时的收尸呢?考虑使用异步通知机制,让孩子变成僵尸的时候给父亲发一个信号,父亲接收到信号立即处理,如果有多个孩子同时退出变成僵尸,而相同的信号会被淹没怎么解决?
2.3重要的API函数
2.3.1创建一个新的进程
因此我们会接受到两个返回值!
测试
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t x;
x = fork();
if(x > 0)
{
printf("[父进程打印父进程ID:%d] I am the parent \n",(int)getpid());
}
else
{
printf("[子进程中打印父进程ID:%d] I am the child \n",(int)getppid());
}
return 0;
}
结果:
注意:
1、任何一个父进程结束后。他的子进程的父进程就变成的他的祖父进程。
2.3.2复制出来的孩子和父亲不干同一件事
我们一般会让子程序去执行一个预先准备好的ELF文件或脚本,用以覆盖从父进程复制过来的代码,下面介绍一下加载ELF文件或脚本的接口函数:
注意:
1、被加载的文件的参数列表必须以自身名字开始,以NULL为结尾。比如要加载执行当前目录下的一个叫做a.out的文件,需要一个参数“abcd”,那么正确的调用是:
execl("./a.out","a.out","abcd",NULL);
或者 const char* argv[3]={"a.out","abcd",NULL};
execv("./a.out","argv");
2、exec函数簇成功执行后,原有的程序代码都将被指定的脚本或文件覆盖,因此这些函数一旦成功后面的代码是无法执行的,他们也是无法返回的。
测试
演示:子进程创建出来后执行的代码,以及如何加载这个指定的程序。被子进程加载的示例代码:
假设在当前目录下有一个需要子进程执行的文件test,父进程需要让子进程去执行这个test文件。
父进程代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char const *argv[]) { pid_t x; x = fork(); if(x > 0) { printf("[父进程打印父进程ID:%d] I am the parent \n",(int)getpid()); } else { printf("execl之前的程序不会被覆盖\n"); printf("[子进程中打印父进程ID:%d] I am the child \n",(int)getppid()); execl("./test","test",NULL); printf("execl之后的程序被覆盖\n"); } return 0; }
创建子进程执行的文件
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { printf("[我是子进程执行的任务,子进程执行的ID为:ID:%d] I am chaild \n",(int)getpid()); return 0; //exit(0); }
结果:
结论:execl类的函数之后的代码会被覆盖掉!
如何让shall提示行不要夹杂在程序输出结果之间?
为什么shall会夹杂在输出结果之间呢?这是因为在子进程运行结束之前父进程就结束了。
如何让子进程运行结束之后再让父进程进行?子进程退出的状态又怎么传递给父进程呢?使用exit()/_exit()来退出并传递退出值,使用wait()/waitpid()来使父进程阻塞等子进程,顺便收尸。
2.3.3退出本进程

_exit直接进入内核,exit则先执行一些清除处理(在进程退出之前要检查文件状态,将文件缓冲区中的内容写回文件)再进入内核。
调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin,stdout,stderr…).exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前会刷新。
exit()与_exit()函数最大的区别在于:exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。由于Linux标准函数中,“缓冲I/O”的操作,其特征即对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续读出若干条记录,在下次读文件时就可以直接从内存的缓冲区读取;同样每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度。
这时如果用_exit()函数直接将进程关闭,缓冲区的数据将会丢失。
原文链接:https://blog.youkuaiyun.com/TKDwave520/article/details/11702491
测试
测试:exit(0)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { printf("[我是子进程执行的任务,子进程执行的ID为:ID:%d] I am chaild ",(int)getpid()); sleep(1); printf("heheheheh"); exit(0); return 0; //exit(0); }
结果:
jiejie@DESKTOP-1HU808H:/mnt/e/嵌入式视频教程/粤嵌/05 系统编程/Demo/进程入门$ gcc test.c -o test jiejie@DESKTOP-1HU808H:/mnt/e/嵌入式视频教程/粤嵌/05 系统编程/Demo/进程入门$ ./test [我是子进程执行的任务,子进程执行的ID为:ID:289] I am chaild heheheheh
测试_exit(0)
代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { printf("[我是子进程执行的任务,子进程执行的ID为:ID:%d] I am chaild ",(int)getpid()); sleep(1); printf("heheheheh"); _exit(0); //exit(0) }
结果:
jiejie@DESKTOP-1HU808H:/mnt/e/嵌入式视频教程/粤嵌/05 系统编程/Demo/进程入门$ gcc test.c -o test jiejie@DESKTOP-1HU808H:/mnt/e/嵌入式视频教程/粤嵌/05 系统编程/Demo/进程入门$ ./test jiejie@DESKTOP-1HU808H:/mnt/e/嵌入式视频教程/粤嵌/05 系统编程/Demo/进程入门$
2.3.4父进程等待子进程退出
使用wait()/waitpid()来获得子进程正常退出的退出值,这两个函数还可以使得父进程阻塞等待子进程的退出,并将子进程的状态切换为EXIT_DEAD便于系统释放子进程资源。
wait()阻塞等待 waitpid()非阻塞等待
注意:退出状态不是退出值,退出状态包含了退出值。可以使用这两个等待函数获取子进程的退出状态,则可以使用以下的宏来解析:
测试
代码
子进程执行文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//#define ABORT
int main(void)
{
printf("[我是子进程执行的任务,子进程执行的ID为:ID:%d] I am chaild ",(int)getpid());
#ifdef ABORT
abort();//自杀
#else
exit(7);//正常退出,退出值为7
#endif
}
进程创建
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t x;
x = fork();
if(x > 0)
{
int chaild_return_status;
wait(&chaild_return_status);//等待子进程退出
if(WIFEXITED(chaild_return_status))//判断子进程是否正常退出
{
printf("child exit normally\n");
}
if(WIFSIGNALED(chaild_return_status))//判断子进程是否被信号杀死
{
printf("child killed by signal :%u\n",WIFSIGNALED(chaild_return_status));
}
printf("[父进程打印父进程ID:%d] I am the parent \n",(int)getpid());
}
else
{
execl("./test","test",NULL);
}
return 0;
}
子程序正常退出
子程序自杀
3.进程之间互相通讯
进程间的通讯(IPC)方式,总归起来有以下:
1、无名管道(PIPE)方式和有名管道(FIFO)
无名管道是最简单常用于一对一的亲缘进程间的通讯方式,有名管道存在于文件系统之中,提供写入原子特征。
2、信号(Signal)
信号是唯一一种异步通信方式
3、system V-IPC之共享内存
共享内存的效率最高,但是要结合信号量等同步互斥机制一起使用
4、system V-IPC之消息队列
消息队列提供一种带简单消息标识的通信方式。
5、system V-IPC之信号量。
6、套接字。
套接字是一种更为广泛意义上的进程间通信方式-----他允许进程间夸网络。
3.1管道
无名管道(PIPE)
特征:
1、没有名字,因此无法使用open()
2、只能用于亲缘进程间(比如父子进程,兄弟进程,祖孙进程。。。。)通信。
3、半双工:读写端分开
4、写入操作不具有原子性,因此只能用于一对一的简单通信。
5、不能使用lseek()来定位。
PIPE是一种特殊的文件,它是一种文件但是没有名字!因此一般进程无法使用open()来获取他的买描述符,他只能在一个进程中被创建出来,然后通过继承的方式将他的文件描述符传递给子进程,这就是为什么PIPE只能用于亲缘进程间通信的原因。
PIPE不同于一般文件的显著之处是:他有两个文件描述符!一个只能来读,一个只能来写,并且他对写操作不做任何保护!即如果多个进程对PIPE进行写那么这些数据时相互践踏的。第一个描述符用来读,第二个描述符用来写。
父进程没有使用PIPE的写端描述符fd[1],同理子进程也并没有使用PIPE的读端描述符fd[0]
例程测试
通过PIPE向父进程发送一段数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd[2];//用来存放PIPE的两个文件描述符
if(pipe(fd) == -1)//创建PIPE,并将文件描述符放进fd[2]中
{
perror("pipe()");
exit(1);
}
pid_t x;
x = fork();//创建一个子进程,他会继承PIPE的描述符
if(x > 0)
{
char buf[30];
bzero(buf,30);//清空
read(fd[0],buf,30);//父进程来读
printf("from child:%s",buf);
}
else
{
char *s = "hello , i am your child\n";
write(fd[1],s,strlen(s));
//execl("./test","test",NULL);
}
close(fd[0]);//关闭文件描述符
close(fd[1]);
return 0;
}
通过PIPE子进程和父进程之间相互通讯
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void Copy_String(char *addr,char *copy_string)
{
int i =0;
while(copy_string[i++] != '\n')
{
addr[i] = copy_string[i];
}
return ;
}
int main(int argc, char const *argv[])
{
int fd[2];//用来存放PIPE的两个文件描述符 用来父亲读取,儿子发送
int fd_2[2];//用来父亲发送儿子读取
if(pipe(fd) == -1)//创建PIPE,并将文件描述符放进fd[2]中
{
perror("pipe()");
exit(1);
}
if(pipe(fd_2) == -1)
{
perror("pipe()_2");
exit(1);
}
pid_t x;
x = fork();//创建一个子进程,他会继承PIPE的描述符
if(x > 0)
{
close(fd[1]);
close(fd_2[0]);
char buf[30];
bzero(buf,30);//清空
read(fd[0],buf,30);//父进程来读
printf("from child:%s",buf);
//bzero(buf,30);
Copy_String(buf,"hello ,i am your father\n");
write(fd_2[1],buf,strlen(buf));//父进程来写
close(fd[0]);//关闭文件描述符
close(fd_2[1]);
}
else
{
close(fd[0]);
close(fd_2[1]);
char child_read_buf[30] ;
char *s = "hello , i am your child\n";
write(fd[1],s,strlen(s));//子进程来写
bzero(child_read_buf,30);//清空
read(fd_2[0],child_read_buf,30);//子进程来读
printf("child read:%s\n",child_read_buf);
close(fd[1]);
close(fd_2[0]);
//execl("./test","test",NULL);
}
return 0;
}
结果:
第一句是父进程打印的来自孩子的消息
第二句是子进程打印来自父亲的消息
有名管道(FIFO)

类比成老师和学生上课的情景->写者(老师),读者(学生)。老师不讲学生就等老师讲(无数据,读阻塞等待)。学生没有消化知识,老师等学生消化(缓冲区满,写阻塞等待) 。。。
示例:
写管道
#include "FIFO_test.h"
int main(int argc, char const *argv[])
{
/*
access函数的原型定义在<unistd.h>头文件中,其声明如下:
#include <unistd.h>
int access(const char *pathname, int mode);
其中,pathname参数指定了文件的路径和文件名,例如/home/book/file.txt。
mode参数则用于指定检查的权限类型,可以是以下几种:
F_OK(值为0):检查文件是否存在。
X_OK(值为1):检查是否有执行文件的权限。
W_OK(值为2):检查是否有写入文件的权限。
R_OK(值为4):检查是否有读取文件的权限。
可以使用逻辑或(|)操作符组合这些模式,例如W_OK | R_OK,以同时检查读写权限。
*/
if(access(FIFO_name,F_OK))//判断这个文件是否存在
{
/*
mkfifo 是一个在Linux系统编程中常用的函数,用于创建一个命名管道(FIFO)。
命名管道允许不相关的进程之间进行通信,这是匿名管道(仅限于有共同祖先的进程间通信)所不能做的。
命名管道在文件系统中是可见的,并通过路径名来访问。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
其中,pathname 是管道文件的路径名,mode 是设置管道的权限。
函数成功时返回0,失败时返回-1,并设置errno以指示错误原因。
*/
mkfifo(FIFO_name,0644);//不存在则创建一个有名管道
}
printf("等待以读的形式打开管道\n");
/*
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname:文件路径
flags:以什么权限打开文件
mode :新建文件时有效,用于设置文件权限
返回值:成功则返回 文件描述符,否则返回 -1
*/
int fifo = open(FIFO_name,O_WRONLY);//以只写方式打开FIFO
char msg[20];
bzero(msg,20);
fgets(msg,20,stdin);//从标准输入获取信息
int n = write(fifo,msg,strlen(msg));
printf("%d bytes have been send.\n",n);
close(fifo);
return 0;
}
读管道
#include "FIFO_test.h"
int main(int argc, char const *argv[])
{
if(access(FIFO_name,F_OK))
{
if(mkfifo(FIFO_name,0666))//创建有名管道
{
perror("mkfifo error");
}
}
printf("等待以写的形式打开管道\n");
int fifo = open(FIFO_name,O_RDONLY);//以只读的方式打开
printf("rose 打开管道成功\n");
if(fifo == -1)
{
printf("rose Open Err\n");
exit(0);
}
char msg[20];
bzero(msg,20);
int ret_val = read(fifo,msg,20);//将数据从FIFO中读出
printf("rose read %d 个字节from FIFO :%s",ret_val,msg);
close(fifo);
/* code */
return 0;
}
头文件
#ifndef __FIFO_TEST_H
#define __FIFO_TEST_H
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#define FIFO_name "/tmp/fifotest" //tmp目录一般用于存放临时文件
//#define FIFO_name "/usr/fifotest" //管道路径不能在当前路径下
//#define FIFO_name "./fifotest" 管道路径不能在当前路径下
#endif
注意:创建管道的路径只能在tmp目录下
3.2信号
linux中的信号
使用:kill -l 命令可以查看信号


相关API函数
向指定进程或者进程组,发送一个指定的信号
kill()并不是去杀死“某人”,除非发送的是一个致命的信号,单纯的发送一个信号并不会致对方于死地。
例程:子进程10s后杀死父进程
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t id = fork();
if(id == 0)//子进程
{
int i = 0;
pid_t father_pid ;
father_pid = getppid();//获取父进程PID
for(;i<10;i++)
{
printf("child sleep %d s\n",i);
sleep(1);//睡1s
}
kill(father_pid,9);
printf("i kill my father\n");
}
if(id > 0)
{
while(1)
{
printf("i am father ,i not die\n");
sleep(1);
}
}
return 0;
}
捕捉一个指定的信号
这个函数一般和kill()配套使用,目标进程必须先使用signal()来为某个信号设置一个相应函数,或者设置忽略某个信号,才能改变信号缺省行为,这个过程被称为“信号的捕捉”。
注意,对于一个信号的“捕捉”可以重复进行,signal()函数将会返回前一次设置的信号函数指针。对于所谓的信号相应函数的接口,规定必须是:void(*)(int);
测试:子进程十秒后发一个信号让父进程执行动作。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
//信号回调函数
void signal_handl(int sig)
{
printf("i am father,i get sig:%d\n",sig);
exit(0);
}
int main(int argc, char const *argv[])
{
pid_t id = fork();
if(id == 0)//子进程
{
int i = 0;
pid_t father_pid ;
father_pid = getppid();//获取父进程PID
for(;i<10;i++)
{
printf("child sleep %d s\n",i);
sleep(1);//睡1s
}
kill(father_pid,3);
printf("i tell my father\n");
}
if(id > 0)
{
while(1)
{
/* void (*signal(int sig,void(*func)(int)))(int) */
signal(3,signal_handl);
printf("i am father ,run sig_handle over\n");
sleep(1);
}
}
return 0;
}
结论:没捕捉到的时候程序不会挂起
自己给自己发送一个指定信号
将本进程挂起,直到收到一个信号
注意:pause()是在响应函数之后,随后再返回的。如果想要一个进程持续的接收信号,也许可以写一个死循环来不断地pause()。
测试:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t id = fork();
if(id == 0)//子进程
{
int i = 0;
pid_t father_pid ;
father_pid = getppid();//获取父进程PID
for(;i<10;i++)
{
printf("child sleep %d s\n",i);
sleep(1);//睡1s
}
kill(father_pid,3);
printf("i tell my father\n");
}
if(id > 0)
{
while(1)
{
pause();//挂起程序后后面程序无法执行
printf("i am father ,run sig_handle over\n");
sleep(1);
}
}
return 0;
}
进程接收到信号后挂起后,后面的程序无法执行。
信号集操作函数
如果一个进程临时不想响应某个或者某些信号,可以通过设置“阻塞掩码(block mask)”来达到此目的。在设置信号的阻塞掩码时,并不一定挨个的设置,而是可以多个信号同时设置,这时就需要用到信号集。
阻塞或者解除一个或者多个信号
如果对原有的阻塞信号不感兴趣,可以将oldset 设置为NULL。注意信号的阻塞不同于忽略,阻塞指的是暂时将信号挂起,不响应他,待解除阻塞后还可以处理这个信号,忽略就是直接把信号丢弃了。
例程:捕捉信号,创建信号集,先阻塞信号集,给自己发送信号,等2s,解除信号集阻塞,结果:看睡两秒后是否响应
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
//信号响应函数
void signal_handl(int sig)
{
printf("信号响应函数响应信号%d\n",sig);
}
int main(int argc, char const *argv[])
{
// 设置信号响应的函数
signal( 3 , signal_handl );
signal( 4 , signal_handl );
sigset_t sig_call ;//创建信号集
sigemptyset( &sig_call );//清空信号集
sigaddset(&sig_call,3);//将信号3添加到信号集
sigaddset(&sig_call,4);
sigprocmask(SIG_BLOCK,&sig_call,NULL);
printf("将信号阻塞,不让相应函数去相应\n");
// 给自己发送 3.4 号信号
raise(3);
raise(4);
sleep(2);
printf("将信号解除阻塞,让响应函数去相应\n");
sigprocmask(SIG_UNBLOCK,&sig_call,NULL);
return 0;
}
向某进程发送一个指定的信号,同时携带一些数据
类似于kill()和signal(),sigqueue()和sigaction()也是这样的搭档只是后者可以处理带额外的数据信号。
union sigval{int sigval_int ;void * sigval_prt ;};
换句话说: 利用 siqqueue( )发送信号的同时可以携带一个整型数据或者一个 void 型指针,目标
捕捉一个指定的信号,且可以通过扩展响应函数来获取信号携带的额外数据
struct sigaction{void ( * sa_handler )( int );void ( * sa_sigaction )( int , siginfo_t * , void * );sigset_t sa_mask ;int sa_flags ;void ( * sa_restorer )( void );};sa_handler:此参数和 signal() 的参数 handler 相同,代表新的信号处理函数
sa_sigaction:另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
sa_mask:成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
sa_flags:用来设置信号处理的其他相关操作,下列的数值可用:
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_NODEFER:一般情况下,当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER 标记,那么在该信号处理函数运行时,内核将不会阻塞该信号。即在信号处理函数执行期间仍能发出这个信号。
SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
re_restorer 成员则是一个已经废弃的数据域,不要使用。
扩展响应函数
该函数的参数列表详情:第一个参数:int 型,就是触发该函数的信号。第二个参数:siginfo_t 型指针,指向如下结构体:1 siginfo_t2 {3 int si_signo ; // si_signo 、 si_errno 和 si_code 对所有信号都有效4 int si_errno ;5 int si_code ;6 int si_trapno ; // 以下成员只对部分情形有效,详情见下面的注解7 pid_t si_pid ;8 uid_t si_uid ;9 int si_status ;10 clock_t si_utime ;11 clock_t si_stime ;12 sigval_t si_value ;13 int si_int ; // 整型参数14 void * si_ptr ; // 指针参数15 int si_overrun ;16 int si_timerid ;17 void * si_addr ;18 long si_band ;19 int si_fd ;20 short si_addr_lsb ;21 }
测试:
#include <stdio.h> #include <signal.h> #include <strings.h> #include <sys/types.h> #include <unistd.h> //捕获信号后的相应函数 void func(int sig, siginfo_t * info , void * arg ) { printf("sig:%d , info: %s arg:%s \n" , sig , (char * )info->si_ptr , (char*)arg ); } int main(int argc, char const *argv[]) { // 定义ACT结构体并设置其信息 struct sigaction act ; bzero(&act , sizeof(act)); // 清空结构体 act.sa_sigaction = func ; act.sa_flags |= SA_SIGINFO; // 设置捕获的信号响应函数 if( sigaction( 3 , &act, NULL )) { perror("设置捕获失败!!"); return -1 ; } // 设置好携带的参数 union sigval value; value.sival_int = 1024 ; // 设置整型数组 value.sival_ptr = "Hello Even"; // 设置一个地址(指针)可以是任意类型的指针 // 发送信号 pid_t pid = getpid( ); if(sigqueue(pid , 3 , value)) { perror("发送信号失败!!"); return -1 ; } printf("发送信号成功!!\n"); sleep(1); return 0; }
注意事项:
1、
//捕获信号后的相应函数
void func(int sig, siginfo_t * info , void * arg )第一个参数是响应的信号
第二个参数是一个结构体,主要用于接收到发送信号函数传过来的数据
第三个一般用不到
2、
// 定义ACT结构体并设置其信息
struct sigaction act ;
act.sa_sigaction = func ;//信号响应函数
act.sa_flags |= SA_SIGINFO;//如何去相应这个信号,具体查看这个可以传递的宏定义// 设置捕获的信号响应函数
if( sigaction( 3 , &act, NULL ))
{
perror("设置捕获失败!!");
return -1 ;
}
3、携带的参数可以是一个整形数据,也可以是任意一个指针,只是数据和指针在接收时要在1 siginfo_t类型的结构体中获取不同的成员即可,整形成员为:si_int,指针为:si_ptr
这是一个联合体,一次只能代表一个成员!!!
// 设置好携带的参数
union sigval value;
value.sival_int = 1024 ; // 设置整型数数据
value.sival_ptr = "Hello Even"; // 设置一个地址(指针)可以是任意类型的指针
问题与解决
程序运行之后出现,编译没错,函数不能相应的情况解决如下:将WSL升级为WSL2
1. 先参考https://docs.microsoft.com/zh-cn/windows/wsl/install-win102. 以管理员身份打开 PowerShell 并运行:dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
3. 下载WSL2_linux内核最新包 , 并安装https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi
5. 查看版本号和名称1 wsl -- list -- verbosewsl -l -v
3 例如:4 PS C : \ WINDOWS \system32 > wsl - l - v5 NAME STATE VERSION6 * Ubuntu - 18.04 Stopped 16. 设置默认使用 WSL 21 wsl -- set - version < distribution name > < versionNumber >23 例如:4 PS C : \ WINDOWS \system32 > wsl -- set - version Ubuntu - 18.04 25 正在进行转换,这可能需要几分钟时间 ...6 有关与 WSL 2 的主要区别的信息,请访问 https : // aka . ms / wsl27 转换完成。7. 检查是否成1 PS C : \ WINDOWS \system32 > wsl - l - v2 NAME STATE VERSION3 * Ubuntu - 18.04 Stopped 2 //2表示升级到版本2问题: 解决:使用管理员身份运行 《命令提示符》1、输入命令:bcdedit /set hypervisorlaunchtype Auto
2、重启再设置默认WSL2一次就可以了 <步骤6>
//下面的可以不需要隐藏终端的路径:1. 打开1 vim . bashrc 2. 修改配置脚本方法1:在最后一行添加1 # export PS1 = '\u@\h: '方法2:1 if [ "$color_prompt" = yes ]; then2 # PS1 = '${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;343 PS1 = '${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\[\033[00m\]:\[\033[01;34m\4 else5 # PS1 = '${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '6 PS1 = '${debian_chroot:+($debian_chroot)}\u@\h:\W\$ '7 fi3. 重启终端或者重新生效该脚本1 $ source . bashrc
3.3 System-V IPC
3.3.1 获取键值
使用:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
//key_t ftok(const char *pathname, int proj_id);
int main(int argc, char const *argv[])
{
key_t My_Key = ftok("./", 1);
printf("My_Key:%d\n",My_Key);
return 0;
}
注意:路径是字符串
1 查看消息队列: ipcs - q2 查看共享内存: ipcs - m3 查看信号量: ipcs - s4 查看所有的 IPC 对象: ipcs - a56 删除指定的消息队列: ipcrm - q MSG_ID 或者 ipcrm - Q msg_key7 删除指定的共享内存: ipcrm - m SHM_ID 或者 ipcrm - M shm_key8 删除指定的信号量: ipcrm - s SEM_ID 或者 ipcrm - S sem_key
3.3.2消息队列(MSG)

消息队列的使用方法一般是:
1、发送者
a)获取消息队列ID
b)将数据放入一个附带有标识得特殊结构体,发送给消息队列。
2、接收者:
a)获取消息队列ID
b)将指定标志的消息读出。
发送者和接收者不再使用消息队列时,及时删除它以释放系统资源。
消息队列(MSG)相关API
获取消息队列ID
发送接收消息
一定要注意:发送消息的结构体是自己要配置的,第一个成员必须是long类型的发送的消息可以是自己设定的任意类型!!!!

例程:
设置或者捕获消息队列的相关属性
测试:
发送一段数据给接收端,接收端在没接收到数据之前是阻塞的状态
//头文件
#ifndef __HEAD4MSG_H
#define __HEAD4MSG_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSGSIZE 64 //单个消息最大字节数
#define PROJ_PATH "." //使用当前路径来产生消息队列的键值key
#define PROJ_ID 1//将消息队列ID设置为1
#define J2R 1L //Jack发送给Rose的消息标识
#define R2J 2L//Rose发给Jack的消息标识
struct msgbuf //带标识的消息结构体
{
long mytype;
char mtext[MSGSIZE];
};
#endif
//发送函数
#include "head4_msg.h"
int main()
{
key_t key = ftok(PROJ_PATH,PROJ_ID);//获取消息队列键值
int msgid = msgget(key,IPC_CREAT | 0666);//通过键值获取消息队列的ID
struct msgbuf message;
bzero(&message,sizeof(message));//
message.mytype = J2R;
strncpy(message.mtext,"I love Rose.\n",MSGSIZE);
if(msgsnd(msgid,&message,strlen(message.mtext),0) != 0)
{
perror("msgsnd()error");
exit(1);
}
return 0;
}
//接收函数
#include "head4_msg.h"
int main()
{
key_t key = ftok(PROJ_PATH,PROJ_ID);//获取消息队列键值
int msgid = msgget(key,IPC_CREAT | 0666);//通过键值获取消息队列的ID
struct msgbuf buf;
bzero(buf.mtext,sizeof(buf.mtext));//
if(msgrcv(msgid,&buf,MSGSIZE,J2R,0) == -1)
{
perror("msgrcv()error");
exit(1);
}
printf("from rose msg:%s",buf.mtext);
msgctl(msgid,IPC_RMID,NULL);//删除消息队列
return 0;
}
总结
1、使用ipcs -m可以查看当前路径下消息队列的信息
2、消息队列使用简单但是由于内核进行分配内存,检查边界,设置阻塞,以及各种权限监控,使得我们使用起来非常省力,因此不适合用来传输海量数据,能解决这个问题的就是共享内存。
3.3.3共享内存(SHM)
共享内存相关API函数
获取共享内存ID
对共享内存进行映射或者解除映射
注意:1、shmat失败返回(void*) -1不是-1。
2.第2点经常将shmflg设置为0 ,0
表示默认读写权限,shmat(shm_id,NULL,0);
获取或者设置共享内存的相关属性
测试:两个进程通过共享内存进行通讯,这只是一个简单的例子
//写共享内存
// 使用共享内存的一般步骤是:
// 1,获取共享内存对象的 ID
// 2,将共享内存映射至本进程虚拟内存空间的某个区域
// 3,当不再使用时,解除映射关系
// 4,当没有进程再需要这块共享内存时,删除它。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#define SHMSZ 1024
struct shmid_ds shm_stata;
struct Shm_write
{
pid_t process_ID;
unsigned char value;
};
void write_messagefun(void* shm_addr,struct Shm_write* value_addr)
{
struct Shm_write* New_Addr = (struct Shm_write*)shm_addr;
New_Addr->process_ID = value_addr->process_ID;
New_Addr->value = New_Addr->value;
return ;
}
int main(int argc, char const *argv[])
{
struct Shm_write write_message;
write_message.process_ID = getpid();
write_message.value = 'a';
printf("write progress ID:%d\n",write_message.process_ID);
key_t key = ftok(".",1);
//获取共享内存id
int shm_id = shmget(key,SHMSZ,IPC_CREAT|0666);//如果key对应的共享内存不存在则创建他
printf("shm_id:%d\n",shm_id);
//将共享内存映射至本进程虚拟内存空间的某个区域
void* shm_addr;
shm_addr = shmat(shm_id,NULL,0);
/*
在 C 语言中,通常使用特定的值来表示错误情形。对于 shmat 函数来说,返回 (void*) -1 是这一约定。这是一种标准的错误检测方式。
检查返回值是否为 (void*) -1,可以有效地判断共享内存是否成功附加。
*/
if(shm_addr == (void*) - 1)
{
perror("shmat failed");
}
//将数据写入共享内存
/*
可以将进程号写入共享内存
*/
//write_messagefun(shm_addr,&write_message);
fgets((char*)shm_addr,100,stdin);
shmctl(shm_id,IPC_STAT,&shm_stata);
printf("创建这个共享内存的PID:%d\n",shm_stata.shm_cpid);
printf("共享内存的尺寸:%d\n",shm_stata.shm_segsz);
return 0;
}
//读共享内存
// 使用共享内存的一般步骤是:
// 1,获取共享内存对象的 ID
// 2,将共享内存映射至本进程虚拟内存空间的某个区域
// 3,当不再使用时,解除映射关系
// 4,当没有进程再需要这块共享内存时,删除它。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHMSZ 1024
struct shmid_ds shm_stata;
struct Shm_write
{
pid_t process_ID;
//unsigned char value;
char value;
};
int main(int argc, char const *argv[])
{
struct Shm_write read_value;
key_t key = ftok(".",1);//确保与写的时候创建的key相同
//获取共享内存id
int shm_id = shmget(key,SHMSZ,IPC_CREAT|0666);//如果key对应的共享内存不存在则创建他
printf("shm_id:%d\n",shm_id);
//将共享内存映射至本进程虚拟内存空间的某个区域
void* shm_addr;
shm_addr = shmat(shm_id,NULL,0);
/*
在 C 语言中,通常使用特定的值来表示错误情形。对于 shmat 函数来说,返回 (void*) -1 是这一约定。这是一种标准的错误检测方式。
检查返回值是否为 (void*) -1,可以有效地判断共享内存是否成功附加。
*/
if(shm_addr == (void*) - 1)
{
perror("shmat failed");
}
// read_value.process_ID = ((struct Shm_write*) shm_addr)->process_ID;
// read_value.value = ((struct Shm_write*) shm_addr)->value;
// printf("这个消息是进程ID为%d写的\n",read_value.process_ID);
printf("消息内容为:%s\n",shm_addr);
shmctl(shm_id,IPC_STAT,&shm_stata);
printf("创建这个共享内存的PID:%d\n",shm_stata.shm_cpid);
printf("共享内存的尺寸:%d\n",shm_stata.shm_segsz);
return 0;
}
总结:
对于共享内存的多进程,多线程同步和互斥一般不用信号来协调,一般使用信号量!
3.3.4信号量

信号量(SEM)API
获取信号量ID
返回参数为:如果失败返回:(void*)-1;
对信号量进程P/V操作,或者等零操作
3、sembuf结构体成员sem_flag:信号量操作的属性标志,
如果为0,表示正常操作,
如果为IPC_WAIT,使对信号量的操作时非阻塞的。即指定了该标志,调用线程在信号量的值不满足条件的情况下不会被阻塞,而是直接返回-1,并将errno设置为EAGAIN。
如果为SEM_UNDO,那么将维护进程对信号量的调整值,以便进程结束时恢复信号量的状态。
这三个可以用|进行同时设置 与设置权限
获取或者设置信号量的相关属性
测试
使用共享内存加信号量的方式进行进程间通信,并且防止读数据进程没有及时读取而写进程将数据覆盖的情况,写入如果有空间就会写入数据否则只能等待,读取数据如果共享内存有数据资源则读取否则等待。
//写进程
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <errno.h>
union semun
{
int val; /* 当 cmd 为 SETVAL 时使用 */
struct semid_ds *buf; /* 当cmd为IPC_STAT 或IPC_SET 时使用 */
unsigned short *array; /* 当 cmd 为 GETALL 或 SETALL 时使用 */
struct seminfo *__buf;/* 当 cmd 为 IPC_INFO 时使用 */
};
int main(int argc, char const *argv[])
{
//获取共享内存(SHM)的键
key_t SHM_key = ftok("./",1);
//获取SHM的ID,并将之映射到本进程虚拟内存空间中
int shmid = shmget(SHM_key,4096,IPC_CREAT|0666);
if(shmid == -1)
{
printf("jack lin_25 shmget error\n");
exit(1);
}
//映射!!!!!
char* SHM_Addr = shmat(shmid,NULL,0);
if((void*)-1 == SHM_Addr)
{
perror("shm map error");
exit(1);
}
//获取信号量(SEM)的键
key_t SEM_key = ftok("./",2);
//获取信号量(SeM)的ID,若新建则初始化,否则直接获取ID
int sem_id = semget(SEM_key,2,IPC_CREAT|IPC_EXCL|0644);
if(sem_id == -1 && errno == EEXIST)
{
sem_id = semget(SEM_key,2,0644);//直接获取SEM的ID
}
//设置信号量元素的值
//一共有两个 空间、数据
union semun set;
//创建第一个信号量:数据资源个数
set.val = 0 ;
semctl(sem_id , 0, SETVAL , set ); // 初始化数据资源 为 0 //还没有写
//创建第二个信号量:空间资源大小
set.val = 1 ;
semctl(sem_id , 1, SETVAL , set); // 初始化空间资源 为 1 //最大为1个
// 在写入共享内存之前需要先申请一个空间资源
// struct sembuf
// {
// unsigned short sem_num; /* 信号量元素序号(数组下标) */
// short sem_op; /* 操作参数 */
// short sem_flg; /* 操作选项 */
// };
//这个结构体是独立的
//总体:先申请空间P,再设置资源量V
struct sembuf space ={
.sem_num = 1 , // 需要设置的空间资源的元素下标为 1
.sem_flg = 0 , // 设置标记为 0 啥也不选
.sem_op = -1 // -1 表示资源量即将-1 申请资源
};
struct sembuf num ={
.sem_num = 0 , // 需要设置的空间资源的元素下标为 0
.sem_flg = 0 , // 设置标记为 0 啥也不选
.sem_op = 1 // 1 表示资源量即将加1 释放资源
};
int i = 3;
while(i--)
{
// 等待空间资源 如果资源暂时不能得到则会阻塞等待(睡眠)
printf("正在等待空间进行写入数据\n");
semop(sem_id , &space , 1 );//p操作
printf("已经得到空间,正在写入数据:\n");
fgets(SHM_Addr,4096,stdin);
printf("写入数据完成\n");
// 设置 数据资源为1
printf("设置数据资源为1\n");
semop(sem_id , &num , 1 );//v操作
}
shmdt(SHM_Addr);//解除共享内存映射
//删除共享内存和信号量
shmctl(shmid, IPC_RMID, NULL);
semctl(sem_id, 0, IPC_RMID, set);
return 0;
}
//读进程
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <errno.h>
union semun
{
int val; /* 当 cmd 为 SETVAL 时使用 */
struct semid_ds *buf; /* 当cmd为IPC_STAT 或IPC_SET 时使用 */
unsigned short *array; /* 当 cmd 为 GETALL 或 SETALL 时使用 */
struct seminfo *__buf;/* 当 cmd 为 IPC_INFO 时使用 */
};
int main(int argc, char const *argv[])
{
//获取共享内存(SHM)的键
key_t SHM_key = ftok(".",1);
//获取SHM的ID,并将之映射到本进程虚拟内存空间中
int shmid = shmget(SHM_key,4096,IPC_CREAT|0666);
if(shmid == -1)
{
printf("jack lin_15 shmget error\n");
exit(1);
}
//映射
char* SHM_Addr = shmat(shmid,NULL,0);
//获取信号量(SEM)的键
key_t SEM_key = ftok(".",2);
//获取信号量(SeM)的ID,若新建则初始化,否则直接获取ID
int sem_id = semget(SEM_key,2,IPC_CREAT|IPC_EXCL|0644);
if(sem_id == -1 && errno == EEXIST)
{
sem_id = semget(SEM_key,2,0644);//直接获取SEM的ID
}
//设置信号量元素的值
//一共有两个 空间、数据
union semun set;
//创建第一个信号量:数据资源个数
set.val = 0 ;
semctl(sem_id , 0, SETVAL , set ); // 初始化数据资源 为 0 //还没有写
//创建第二个信号量:空间资源大小
set.val = 1 ;
semctl(sem_id , 1, SETVAL , set); // 初始化空间资源 为 1 //最大为1个
// 在写入共享内存之前需要先申请一个空间资源
// struct sembuf
// {
// unsigned short sem_num; /* 信号量元素序号(数组下标) */
// short sem_op; /* 操作参数 */
// short sem_flg; /* 操作选项 */
// };
//这个结构体是独立的
//写进程总体:先申请空间P,再设置资源量V
struct sembuf space ={
.sem_num = 1 , // 需要设置的空间资源的元素下标为 1
.sem_flg = 0 , // 设置标记为 0 啥也不选
.sem_op = 1 // 1 表示资源量即将+1 释放资源 之后 资源变为1
};
struct sembuf num ={
.sem_num = 0 , // 需要设置的数据元素个数下标为 0
.sem_flg = 0 , // 设置标记为 0 啥也不选
.sem_op = -1 // 1 表示资源量即将加1 释放资源
};
int i = 3;
while(i--)
{
// 等待数据资源
printf("等待读取数据\n");
semop(sem_id , &num , 1 );//v操作
printf("读到:%s\n",SHM_Addr);
// 等待空间资源 如果资源暂时不能得到则会阻塞等待(睡眠)
semop(sem_id , &space , 1 );//p操作
}
shmdt(SHM_Addr);//解除共享内存映射
//删除共享内存和信号量
shmctl(shmid, IPC_RMID, NULL);
semctl(sem_id, 0, IPC_RMID, set);
return 0;
}
写进程和读进程 只是PV操作不同
代码解释:
这段代码实现了一个简单的共享内存和信号量的例子,目的是通过信号量机制实现同步和互斥,避免并发访问共享内存时发生冲突。
逐行解释代码:
1. union semun 的定义:
union semun
{
int val; /* 当 cmd 为 SETVAL 时使用 */
struct semid_ds *buf; /* 当cmd为IPC_STAT 或IPC_SET 时使用 */
unsigned short *array; /* 当 cmd 为 GETALL 或 SETALL 时使用 */
struct seminfo *__buf;/* 当 cmd 为 IPC_INFO 时使用 */
};
1.这是一个联合体(union),用于信号量操作时传递不同类型的参数。具体类型取决于 semctl 的操作命令。
2.val 用于设置信号量的初值(常见操作),buf 和 array 用于获取信号量的状态等信息。
2. 获取共享内存(SHM)键:
key_t SHM_key = ftok(".",1);
3.ftok(".", 1) 生成一个基于当前目录(.)和数字 1 的唯一键值(key),作为共享内存的标识符。这个键值会用于后续获取共享内存的标识符。
3. 获取共享内存的 ID,并映射到进程虚拟内存:
int shmid = shmget(SHM_key, 4096, IPC_CREAT | 0666);
if (shmid == -1)
{
printf("jack lin_15 shmget error\n");
exit(1);
}
char* SHM_Addr = shmat(shmid, NULL, 0);
4.shmget 创建或获取一个共享内存段,大小为 4096 字节,权限为 0666(读写权限)。如果成功,返回共享内存的标识符 shmid,否则输出错误信息并退出。
5.shmat 将共享内存映射到当前进程的虚拟地址空间,并返回该内存区域的地址 SHM_Addr。
4. 获取信号量(SEM)的键:
key_t SEM_key = ftok(".", 2);
6.ftok(".", 2) 生成另一个唯一的键值(key),用于后续获取信号量的标识符。
5. 获取信号量的 ID,若信号量已存在则获取其 ID:
int sem_id = semget(SEM_key, 2, IPC_CREAT | IPC_EXCL | 0644);
if (sem_id == -1 && errno == EEXIST)
{
sem_id = semget(SEM_key, 2, 0644);
}
7.semget 用于创建或获取信号量集。这里创建一个包含 2 个信号量的信号量集。IPC_CREAT | IPC_EXCL 标志表示如果信号量不存在则创建新信号量集,0644 是权限设置。如果信号量集已存在,则通过 errno == EEXIST 重新获取已存在的信号量集。
6. 设置信号量的初值:
union semun set;
set.val = 0;
semctl(sem_id, 0, SETVAL, set); // 初始化数据资源为0
set.val = 1;
semctl(sem_id, 1, SETVAL, set); // 初始化空间资源为1
8.semctl 用于控制信号量的操作。这里初始化了两个信号量:
9.第一个信号量(sem_num = 0)初始化为 0,表示数据资源的数量为 0。
10.第二个信号量(sem_num = 1)初始化为 1,表示空间资源的数量为 1。
7. 信号量操作:
struct sembuf space = {
.sem_num = 1,
.sem_flg = 0,
.sem_op = -1
};
struct sembuf num = {
.sem_num = 0,
.sem_flg = 0,
.sem_op = 1
};
11.struct sembuf 是信号量操作结构体,定义了信号量的操作。
12.space 用于申请空间资源。sem_num = 1 表示操作第二个信号量(空间资源),sem_op = -1 表示将该信号量的值减 1,即申请一个空间。
13.num 用于释放数据资源。sem_num = 0 表示操作第一个信号量(数据资源),sem_op = 1 表示将该信号量的值加 1,即释放一个数据资源。
8. 主循环:
while (1)
{
semop(sem_id, &space, 1); // P 操作,申请空间资源
fgets(SHM_Addr, 4096, stdin); // 从标准输入读取数据到共享内存
semop(sem_id, &num, 1); // V 操作,释放数据资源
}
14.semop 用于执行信号量操作。
15.semop(sem_id, &space, 1):P 操作,申请空间资源(即将空间资源信号量减 1,如果当前没有空间则会阻塞等待)。
16.fgets(SHM_Addr, 4096, stdin):读取用户输入并将其存入共享内存。
17.semop(sem_id, &num, 1):V 操作,释放数据资源(即将数据资源信号量加 1,表示数据已经写入)。
9. 为什么要创建两个信号量(空间和数据)?
18.空间资源信号量(sem_num = 1):表示共享内存的可用空间。初始化为 1,表示一开始可以向共享内存中写入数据(即空间可用)。当没有空间时,信号量减为 0,程序会阻塞,等待空间可用。
19.数据资源信号量(sem_num = 0):表示共享内存中是否有有效数据。初始化为 0,表示开始时没有数据。当数据被写入共享内存后,信号量加 1,通知其他进程可以读取数据。
总结:
20.空间资源信号量:用来控制对共享内存的写入操作,避免多个进程同时写入共享内存。
21.数据资源信号量:用来控制读取操作,确保数据写入后才可以被读取。这种设计使得生产者(写入数据的进程)和消费者(读取数据的进程)能够通过信号量协调同步,确保共享内存的访问不会发生冲突。
3.3.5总结
1、他们都需要通过键值获取ID才能进行一系列的操作
2、ipcs指令:查看当前路径下的消息队列,共享内存,信号量信息
从你提供的信息来看,系统中当前有一个共享内存段和一个信号量数组。下面是对信息的解释:
共享内存段:1.key: 0x5830cf14 - 共享内存的键(key),用于识别共享内存。
2.shmid: 1 - 共享内存段的标识符(shmid)。
3.owner: jiejie - 共享内存的所有者。
4.perms: 644 - 权限,表示文件权限(在 UNIX 和 Linux 系统中,644 意味着所有者有读写权限,其他用户有读取权限)。
5.bytes: 4096 - 共享内存的大小,这里是 4096 字节。
6.nattch: 0 - 当前连接到共享内存的进程数量,0 表示没有进程连接。
7.status: (空) - 没有显示任何状态,可能是共享内存段没有被访问或已删除。信号量数组:
8.key: 0x5630cf14 - 信号量的键(key),用于识别信号量数组。
9.semid: 1 - 信号量数组的标识符(semid)。
10.owner: jiejie - 信号量数组的所有者。
11.perms: 644 - 权限,表示文件权限。
12.nsems: 2 - 信号量数组包含的信号量数量,这里有 2 个信号量。启动进程后可能的情况
13.共享内存段:
14.当前没有进程连接到共享内存 (nattch 为 0)。
15.如果你的多进程代码已经运行,但 nattch 仍为 0,可能是进程没有正确连接到共享内存。
16.信号量:
17.信号量数组中有 2 个信号量(nsems 为 2)。这意味着你的程序可能在管理多进程访问共享内存时使用了信号量来同步访问。建议操作:
18.检查进程连接共享内存:
确保你的子进程或父进程正确地调用了 shmat() 来将共享内存映射到它们的地址空间。你可以在进程中使用 shmdt() 来检查是否正确断开了共享内存连接。
19.确保信号量初始化正确:
检查你的信号量数组是否被正确初始化。如果你在多进程中使用信号量进行同步,确保你在每个进程中正确执行了 semop() 来执行 P(锁)和 V(解锁)操作。
20.检查共享内存内容:
如果你想查看共享内存中的内容,可以使用 ipcs -m 查看共享内存段。要查看共享内存的内容,可以使用 ipcs -m 命令,但你需要使用 shmat 将共享内存映射到进程的地址空间进行读取。
21.清理未使用的共享内存和信号量:
如果这些共享内存和信号量段是旧的或未使用的,确保你在程序结束时通过 shmctl() 和 semctl() 删除这些资源,避免系统资源泄漏。如果有进一步的疑问或者需要更多帮助来调试代码,可以随时告诉我。