Linux环境C 进程及进程间通信

进程基本概念:

1、进程与程序

​ 程序就是储存在磁盘上的可执行文件,程序被加载到内存中开始运行进程。

​ 一个程序被多次加载时就生成了多个进程

​ 进程就是处于活动状态的计算机程序

2、进程的分类

​ 进程一般分为三种类型:交互进程、批处理进程、守护进程

​ 守护进程一般处于活跃状态,运行在后台,由系统在开机时通过启动脚本来自动创建

3、 查看进程

​ 简单方式 ps 显示当前用户有控制终端的进程信息

​ 列表方式 ps auxw 显示所有进程详细信息

​ a 所有用户的有控制终端的进程

​ x 无控制终端的进程

​ u 显示进程的详细信息

​ w 以更大的列宽显示

USER 进程的属主

PID 进程的编号

%CPU CPU利用率

%MEM 内核使用率

VSZ 虚拟内存使用字节数

RSS 物理内存使用字节数

TYY 终端设备号 ? 表示无终端控制设备

STAT 进程的状态

​ O 就绪,等待被调度

​ R 运行,Linux系统没有0,就绪也用R表示

​ S 可被唤醒的睡眠,例如系统中断、获得资源、收到信号都可以唤醒它进入运行态

​ D 不可唤醒的睡眠,只能被系统唤醒

​ T 暂停态 收到SIGSTOP信号进入暂停态,收到SIGCONT信号转入运行态

​ W 等待内存分页 (2.6内核后被废弃了)

​ Z 僵尸状态

​ X 死亡状态

​ < 高优先级

​ N 低优先级

​ l 多线程的进程

​ s 进程的领导者

​ L 有内存被锁进内存

​ + 处于后台的进程组

START 进程启动的时间

TIME

COMMAND 启动进程的命令

4、父进程、子进程、孤儿进程与僵尸进程

​ 一个进程可以被另一个进程创建,创建者叫父进程,被创建者叫做子进程,子进程被父进程启动后会在操作系统的调用下同时运行

​ 当子进程先于父进程结束时,子进程会向父进程发送SIGCHLD信号,此时父进程应该去回收子进程的相关资源,如果没有回收,那么子进程就会变成僵尸进程

​ 僵尸进程:该进程已经死亡,但是它的父进程没有立即回收它的相关资源,该进程就进入了僵尸状态

​ 孤儿进程:父进程先于子进程结束,子进程就变成了孤儿进程,此时孤儿进程会被孤儿院(init守护进程)领养,init就是该孤儿进程的父进程

5、进程标识符

​ 每个进程都有一个非负整数表示的唯一表示,即是它的进程ID/PID

​ 进程ID在任何时候都是唯一的,但可以重用,进程一旦结束后,它的PID会被系统 回收,过一段时间才可以重新分配给其他新创建的进程使用(延迟重用)

pid_t getpid(void);
功能:获取当前进程的进程ID

pid_t getppid(void);
功能:获取当前进程的父进程的ID

init的进程ID永远是1

创建进程:

​ int system(const char *command);

​ 功能:执行一个可执行文件,这样就创建了一个子进程

​ 返回值:子进程结束后才返回

​ 该函数的实现调用了fork和waitpid函数,其实相当于创建了一个子进程,该子进程` 加载了可执行文件command

​ pid_t fork(void);

​ 功能:创建子进程

​ 返回值:一次调用两次返回,子进程返回0,父进程返回子进程的ID,当进程的数量 超过系统限制就会创建进程失败,返回-1

​ 通过fork创建的子进程会拷贝父进程(数据段、bss段、堆、栈、IO缓冲区)等数据 区,与父进程共享代码段,子进程会继承父进程的信息处理方式

​ 该函数调用后,父子进程各自独立运行,谁先返回并不确定,但是可以通过睡眠确 定哪个进程先执行

​ for(;😉

​ {

​ fork(); //会资源耗尽

​ }

可以根据返回值的不同来让父进程进入不同的分支,执行不同的代码

通过fork创建子进程可以共享父进程的文件描述符

不同的普通进程不能共享同一个值的文件描述符

pid_t vfork(void)

功能:以加载可执行文件方式来创建子进程

返回值:子进程返回0,父进程返回子进程的PID

子进程先返回,此时子进程并没有创建成功,需要加载一个可执行文件来替换当前子进 程的所有资源,完成替换的子进程才算创建成功,此时父进程才能返回

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

int main(int argc,const char* argv[])
{
	printf(".%d\n",getpid());
	pid_t pid = fork();
	if(0 == pid)
	{
		printf("我是子进程%u 我的父进程是 %u\n",getpid(),getppid());	
		pause();
	}
	else
	{
		printf("我是父进程 %u,我的子进程是 %u\n",getpid(),pid);	
		pause();
	}
}

使用exec系统函数加载可执行文件:

int execl(const char *path, const char *arg, ...);
path:可执行文件的路径
arg:命令行参数,一般第一个就是可执行文件的名字,至少有一个,以NULL结尾

int execlp(const char *file, const char *arg, ...);
file:可执行文件名字,会根据环境变量PATH的路径来查找可执行文件

int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
envp:环境变量表,父进程可以在加载子进程时把环境变量表传递给子进程,这样父子进程就共用一个环境变量表

int execv(const char *path, char *const argv[]);
argv:指针数组,由传递给子进程的字符串组成,以NULL结尾

int execvp(const char *file, char *const argv[]);

int execvpe(const char *file, char *const argv[],
                  char *const envp[]);

注意:exec系列函数正常是不会返回的,当加载子进程加载可执行文件失败时会返回-1

以exec系列函数创建的子进程不会继承父进程的信号处理函数,但是能继承父进程的信号屏蔽

进程的正常退出:

1、在main函数中执行return n,该返回值可以被其父进程接收

2、调用了exit函数,该函数是标准库函数

​ void exit(int status);

​ 功能:在任意时候调用了此函数都可以结束进程

​ status:结束状态码,与return函数中的返回值效果一样

​ 注意:该函数不会返回

​ 进程退出前

int atexit(void (*function)(void));
功能:注册一个进程结束前要执行的函数

int on_exit(void (*function)(int , void *), void *arg);
功能:注册一个进程结束前要执行的函数
intreturn的值或者exit函数的参数
arg:会在进程结束时自动传给function函数

调用exit会进行的事情:
1、先调用事先通过atexit/on_exit注册过的函数,如果都注册了,执行顺序与注册顺序相反
2、冲刷并关闭打开状态下的标准IO流
3、该函数的实现调用了_exit/_Exit	

3、调用 _exit / _Exit函数

void _exit(int status);
功能:结束进程,由系统提供

void _Exit(int status);
功能:结束进程,由标准库提供
	1、它们的参数会被父进程获取到
	2、进程结束前会关闭所有打开状态下的文件描述符(open)
	3、给父进程发送SIGCHLD信号
	4、该函数也不会返回

4、进程的最后一个线程执行返回语句

5、进程的最后一个线程执行pthread_exit函数

进程的异常终止:

1、进程调用了abort函数,产生了SIGABRT信号

2、进程接受某些信号,可以是其他进程发送,也可以是自己发送的,还可能是自己错误导致

3、进程的最后一个线程接收到了"取消"操作,并对此做出响应

这三种结束方式导致父进程无法获取结束状态码,所有叫做异常终止

注意:不管进程是如何结束都会执行同一段代码,关闭所有打开状态下的文件描述符,释放所有的内存

无论

子进程的回收:

对于任何结束方式,都希望父进程能够知道,通过wait、waitpid函数可以知道子进程是如何结束的以及结束状态码

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:等待子进程的结束,并获取结束状态码
返回值:子进程PID
	1、如果所有的子进程都在运行,该函数会阻塞
	2、如果有任意一个子进程结束,立即返回该子进程结束状态码和PID
	3、如果没有子进程则立即返回-1
	
	WIFEXITED(status)	判断子进程是否正常结束,如果是则返回真
	WEXITSTATUS(status)	如果子进程是正常结束的,可以获取正确的结束状态码,只															能获取低八位
	WIFSIGNALED(status)	判断子进程是否正常结束,如果是则返回真
	WTERMSIG(status)	判断子进程是异常终止,可以获取导致异常终止的信号编码
	
注意:
	由于wait函数可能会阻塞,因此不适合放在父进程的业务逻辑中来调用,因此,可以用SIGCHID信号处理函数,在处理函数中来调用wait函数,注意既可以获取结束的子进程的状态,也不会影响父进程的正常业务逻辑
	
pid_t waitpid(pid_t pid, int *status, int options);
功能:指定回收某个或某些进程
pid:
	< -1	等待属于abs(pid)的编号的进程组中的进程结束
	-1		等待任意子进程的结束,功能等同于wait
	0		等待同组的任意进程结束
	>0		等待pid该进程结束
status:进程结束状态码,与wait的等价
options:
	0			阻塞模式,等于wait
	WNOHANG		非阻塞模式,如果没有子进程结束,立刻返回
	WUNTRACED	如果有进程处于暂停状态时,返回该进程的状态码
	WCONTINUED	如果有进程从暂停状态转为继续运行,返回进程的状态码
	
	WIFSTOPPED(status)	判断进程是否处于暂停状态,是则返回真
	WSTOPSIG(status)	获取导致进程暂停的信号
	WIFCONTINUED(STATUS)判断进程是否由暂停态转为运行态,是则返回真
	

练习:利用今天的知识,实现一个system函数

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

int _system(const char* command)
{
	pid_t pid = vfork();
	if(0 == pid)
	{
		return execlp(command,command,NULL);
	}
	else
	{
		int status = 0;
		waitpid(pid,&status,0);
		return status;
	}
}

int main(int argc,const char* argv[],char** environ)
{
	_system("clear");
	printf("-----------");
}

进程间通信:

基本概念:

什么是进程间通信:

是指两个或者多个进程之间交互数据交互数据的过程,因为进程之间是相互独立的,为了能够让多个进程协同工作,必须互相交互数据

进程间通信的分类:(必考)

简单的进程间通信: 信号、文件、环境变量、命令行参数

传统的进程间通信方式:管道文件

XSI进程间通信: 共享内存、消息队列、信号量

网络进程间通信: Socket套接字 本地-socket文件 网络-TCP/IP

传统进程间通信:

管道是UNIX最古老的进程间通信方式,古老意味着所有系统都支持,早期管道都是半双工,现在有些系统的管道是全双工(但是也要假定管道是半双工来实现)

管道就是一种特殊的文件,它的数据在文件中是流动的,读取之后会就会消失,如果文件中没有数据可以读取则会阻塞

有名管道:

​ 基于有文件名的管道文件的通信

​ 编程模型

​ 进程A 进程B

创建管道 …

打开管道 打开管道

写数据 读数据

关闭管道 关闭管道

删除管道 …

创建管道文件:
1、命令 mkfifo file
2、函数:
	int mkfifo(const char *pathname, mode_t mode);
	功能:创建管道文件
	pathname:管道文件路径
	mode:管道文件权限
3、标准库函数
	FILE* popen(const char *command, const char *type);
	功能:打开或创建管道文件
	command:可执行文件名
	type:
		r		文件指针链接到可执行文件的标准输出
		w		文件指针链接到可执行文件的标准输入
		
	int pclose(FILE *stream);
	功能:关闭管道文件
	注意:关闭管道文件的专用函数,一定不能与fclose混用
//	A
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc,const char* argv[])
{
	int fd[2] ={};
	//	获取匿名管道的fd数组
	if(pipe(fd))
	{
		perror("pipe");
		return -1;
	}

	//	创建子进程
	if(fork())
	{
		// 父进程	负责写 关闭读
		close(fd[0]);
		for(;;)
		{
			char buf[256] = {};
			printf(">>>");
			scanf("%s",buf);
			write(fd[1],buf,strlen(buf));
			if(0 == strcmp("quit",buf))
			{
				printf("通信结束\n");
				usleep(5000);
				break;
			}
			usleep(5000);
		}
		close(fd[1]);
	}
	else
	{
		//	子进程 负责读  关闭写
		close(fd[1]);
		for(;;)
		{
			char buf[256] = {};
			read(fd[0],buf,sizeof(buf));
			printf("read:%s\n",buf);
			if(0 == strcmp(buf,"quit"))
			{
				printf("通信结束\n");
				break;
			}
		}
		close(fd[0]);
	}
}
//	B
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>

int main(int argc,const char* argv[])
{
	//打开管道文件
	int fd = open("fifo",O_RDONLY);
	if(0 > fd)
	{
		perror("open");
		return -1;
	}
	
	//读数据
	for(;;)
	{
		char buf[256] = {};
		read(fd,buf,sizeof(buf));
		printf("read:%s\n",buf);
		if(0 == strcmp(buf,"quit"))
		{
			printf("通信结束!\n");
			break;
		}
	}
	//关闭管道文件
	close(fd);
}
匿名管道:

注意:只适合通过fork函数创建的父子进程之间使用

​ 编程模型

​ 父进程 子进程

​ 创建获取一对fd

​ 创建子进程 拷贝一对fd

​ 关闭读 关闭写

​ 写数据 读数据

​ 关闭写 关闭读

int pipe(int pipefd[2]);
功能:创建一个匿名管道文件,
pipefd:用于存储管道文件的fd的数组
	pipefd[0]:用于读
	pipefd[1]:用于写
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc,const char* argv[])
{
	int fd[2] ={};
	//	获取匿名管道的fd数组
	if(pipe(fd))
	{
		perror("pipe");
		return -1;
	}

	//	创建子进程
	if(fork())
	{
		// 父进程	负责写 关闭读
		close(fd[0]);
		for(;;)
		{
			char buf[256] = {};
			printf(">>>");
			scanf("%s",buf);
			write(fd[1],buf,strlen(buf));
			if(0 == strcmp("quit",buf))
			{
				printf("通信结束\n");
				usleep(5000);
				break;
			}
			usleep(5000);
		}
		close(fd[1]);
	}
	else
	{
		//	子进程 负责读  关闭写
		close(fd[1]);
		for(;;)
		{
			char buf[256] = {};
			read(fd[0],buf,sizeof(buf));
			printf("read:%s\n",buf);
			if(0 == strcmp(buf,"quit"))
			{
				printf("通信结束\n");
				break;
			}
		}
		close(fd[0]);
	}
}

XSI进程间通信:

​ X/open 公司执行的用于进程间通信的系统接口

​ XSI进程间通信都需要借助系统内核,需要创建内核对象,内核对象以整数形式返回给用户,相当于文 件描述符,也叫做IPC标识符

​ 文件的创建、打开都需要文件名,IPC内核对象的创建也需要借助IPC键值(整数),必须确保IPC键值独一 无二

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:计算出一个独一无二的IPC键值
pathname:项目路径,不是依赖字符计算的,而是依赖路径的位置,如果提供的是假的路径,可能会计算出系统的IPC键值
proj_id:项目编号
返回值:计算出来的IPC键值
    

共享内存:

基本特点:

两个或者多个进程之间共享一块由内核负责管理维护的内存,该内存可以与进程的虚拟内存空间进行映射

​ 优点:不需要复制信息,是最快的IPC机制
​ 缺点:需要考虑同步访问问题

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:创建\获取共享内存
key:由进程提供的独一无二的IPC键值
size:共享内存的大小,获取共享内存时,此参数无意义,一般设置为0
shmflg:
	IPC_CREAT	创建共享内存
	IPC_EXCL	共享内存已经存在时返回错误
	获取时赋值 0
	mode_flags:创建共享内存时需要提供权限
返回值:IPC标识符,错误返回-1

void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:虚拟内存与共享内存进行映射
shmid:IPC标识符,shmget的返回值
shmaddr:想要映射的虚拟内存地址,给NULL系统会自动操作
shmflg:
	SHM_RDONLY:以只读方式访问共享内存
返回值:与共享内存映射后的虚拟内存的首地址,失败返回(void *) -1

int sgmdt(const void *shmaddr);
功能:取消映射
shmaddr:映射过的虚拟内存地址

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:删除/控制共享内存
shmid:IPC标识符
cmd:
	IPC_STAT	获取共享内存属性	则buf为输出型参数
	IPC_SET		设置共享内存属性	则buf为输入型参数
	IPC_RMID	删除共享内存		  NULL
	struct shmid_ds {
           struct ipc_perm shm_perm;    //所有者的相关信息
           size_t          shm_segsz;   //共享内存字节数
           time_t          shm_atime;   //最后映射时间
           time_t          shm_dtime;   //最后取消映射时间
           time_t          shm_ctime;   //最后改变时间
           pid_t           shm_cpid;    //创建者进程号
           pid_t           shm_lpid;   	//最后映射\取消映射的进程号
           shmatt_t        shm_nattch; 	//当前映射次数
           ...
       };
	struct ipc_perm {
               key_t          __key;  //创建共享内存的PIC键值  
               uid_t          uid;    //当前使用共享内存的用户ID
               gid_t          gid;    //当前使用共享内存的用户组ID
               uid_t          cuid;   //创建共享内存的用户ID
               gid_t          cgid;   //创建共享内存的用户组ID
               unsigned short mode;   //共享内存权限
               unsigned short __seq;  //共享内存的序列号
       };

​ 编程模型

​ 进程A 进程B

​ 创建共享内存 获取共享内存

​ 映射共享内存 映射共享内存

​ 写数据后通知读 接收读通知后读数据

​ 接收读通知后读数据 写数据后通知读

​ 取消映射 取消映射

​ 删除共享内存

//	进程A
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>

int shmid;
char* shm;

//接收到读消息信号处理函数,负责从共享内存中读数据
void sigrtmin(int num)
{
	//	接收读消息并读取数据
	printf("read:%s\n",shm);
	if(0 == strcmp("quit",shm))
	{
		printf("通信结束\n");
		//取消映射
		if(shmdt(shm))
		{
			perror("shmdt");
			exit(-1);
		}
		usleep(1000);//等待对面进程取消映射
		//删除共享内存
		if(shmctl(shmid,IPC_RMID,NULL))
		{
			perror("shmctl");
			exit(-1);
		}
	}
}

int main(int argc,const char* argv[])
{
	//	注册信号处理函数
	signal(SIGRTMIN,sigrtmin);
	printf("我是进程%u\n",getpid());

	pid_t pid = 0;
	printf("请输入与我通信的进程PID:");
	scanf("%u",&pid);

	//	创建共享内存
	shmid = shmget(ftok(".",110),4096,IPC_CREAT|0644);
	if(0 > shmid)
	{
		perror("shmget");
		return -1;
	}

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

	//	写入数据并通知其他进程
	for(;;)
	{
		printf(">>>");
		scanf("%s",shm);
		kill(pid,SIGRTMIN);
		if(0 == strcmp(shm,"quit"))
		{
			printf("通信结束\n");
			break;
		}

	}
	//	取消映射
	if(shmdt(shm))
	{
		perror("shmdt");
		return -1;
	}
	
	usleep(1000);//等待对面进程取消映射

	//删除共享内存
	if(shmctl(shmid,IPC_RMID,NULL))
	{
		perror("shmctl");
		return -1;
	}

}
//	进程B
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>

char* shm;

//接收到读消息信号处理函数,负责从共享内存中读数据
void sigrtmin(int num)
{
	//	接收读消息并读取数据
	printf("read:%s\n",shm);
	if(0 == strcmp("quit",shm))
	{
		printf("通信结束\n");
		//取消映射
		if(shmdt(shm))
		{
			perror("shmdt");
			exit(-1);
		}
		exit(0);//已经接收到quti,需要结束整个程序
	}
}

int main(int argc,const char* argv[])
{
	//	注册信号处理函数
	signal(SIGRTMIN,sigrtmin);
	printf("我是进程%u\n",getpid());

	pid_t pid = 0;
	printf("请输入与我通信的进程PID:");
	scanf("%u",&pid);

	//	获取共享内存
	int shmid = shmget(ftok(".",110),0,0);
	if(0 > shmid)
	{
		perror("shmget");
		return -1;
	}

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

	//	写入数据并通知其他进程
	for(;;)
	{
		printf(">>>");
		scanf("%s",shm);
		kill(pid,SIGRTMIN);
		if(0 == strcmp(shm,"quit"))
		{
			printf("通信结束\n");
			break;
		}

	}
	//	取消映射
	if(shmdt(shm))
	{
		perror("shmdt");
		return -1;
	}
}

消息队列:

基本特点:是由内核管理维护的数据链表,通过消息类型来收发数据。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:创建\获取消息队列
key:IPC键值
msgflg:
	IPC_CREAT	创建消息队列
	IPC_EXCL	如果消息队列已存在则会返回错误
	如果获取消息队列	直接给0
	如果是创建消息队列,记得提供权限mode
返回值:成功返回IPC标识符,错误返回-1

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息列表发送数据
msqid:IPC标识符
msgp:要发送的消息的首地址
 		struct msgbuf {
               long mtype;       //	消息类型
               char mtext[n];    //	数据
         	  };
msgsz:数据的字节数,不包含消息类型
msgfl:
	阻塞一般写0
	IPC_NOWAIT	当消息队列满时,不等待立即返回
    
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列中读取数据
msqid:IPC标识符
msgp:存储数据的首地址
msgsz:结构体中数据的最大字节数
msgtyp:消息类型
	0	读取消息队列第一条消息
	>0	读取消息队列中消息类型等于msgtyp的消息
	<0	读取消息类型小于abs(msgtyp)消息,如果多个则读取最小的消息
msgflg:
	IPC_NOWAIT	当消息队列中没有匹配的消息类型时,立即返回
	MSG_EXCEPT	如果msgtyp>0,则读取第一个消息类型不是msgtyp的消息
	MSG_NOERROR	如果包含此标志,则读取msgsz个字节,如果不包含此标志,消息实际长度>msgsz,则会返回错误而不读取数据。
返回值:成功读取到的字节数

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除\获取消息队列的属性
msqid:IPC标识符
cmd:
	IPC_STAT	获取信息队列的属性
	IPC_SET		设置消息队列的属性
	IPC_RMID	删除消息队列
buf:
 		struct msqid_ds {
               struct ipc_perm msg_perm;   //	属主信息
               time_t          msg_stime;    //	最后发送时间
               time_t          msg_rtime;    //	最后接收时间
               time_t          msg_ctime;    //	最后修改时间
               unsigned long   __msg_cbytes; //	当前消息队列的字节数
               msgqnum_t       msg_qnum;   	 //	当前消息队列的消息数
               msglen_t        msg_qbytes;   //	队列中最大消息的字节数
               pid_t           msg_lspid;    //	最后发送者的PID
               pid_t           msg_lrpid;    //	最后接收者的PID
           };

​ 编程模型

​ 进程A 进程B

​ 创建消息队列 获取消息队列

​ 发送消息 接收消息

​ 接收消息 发送消息

​ 删除消息队列

//	进程A
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "messgeage.h"

int main(int argc,const char* argv[])
{
	//	创建消息队列
	int msgid = msgget(ftok(".",120),IPC_CREAT|0644);
	if(0 > msgid)
	{
		perror("msgget");
		return -1;
	}

	Msg msg = {};
	for(;;)
	{
		msg.type = 5;
		printf(">>>");
		scanf("%s",msg.data);

		//发送消息
		if(msgsnd(msgid,&msg,strlen(msg.data)+1,0))
		{
			perror("msgsnd");
			break;
		}
		if(0 == strcmp("quit",msg.data)) break;


		//接收消息
		if(0 == msgrcv(msgid,&msg,MESMAX,6,0))
		{
			perror("msgrcv");
			break;
		}
		printf("recv:%s\n",msg.data);
		if(0 == strcmp("quit",msg.data)) break;
	}
	
	printf("通信结束!\n");

	usleep(1000);

	//	删除消息队列
	if(msgctl(msgid,IPC_RMID,NULL))
	{
		perror("msgctl");
		return -1;
	}
	return 0;
}
//	进程B
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include "messgeage.h"

int main(int argc,const char* argv[])
{
	//	获取消息队列
	int msgid = msgget(ftok(".",120),0);
	if(0 > msgid)
	{
		perror("msgget");
		return -1;
	}

	Msg msg = {};
	for(;;)
	{
		//接收消息
		if(0 == msgrcv(msgid,&msg,MESMAX,5,0))
		{
			perror("msgrcv");
			break;
		}
		printf("recv:%s\n",msg.data);
		if(0 == strcmp("quit",msg.data)) break;


		msg.type = 6;
		printf(">>>");
		scanf("%s",msg.data);
		//发送消息
		if(msgsnd(msgid,&msg,strlen(msg.data)+1,0))
		{
			perror("msgsnd");
			break;
		}
		if(0 == strcmp("quit",msg.data)) break;
	}
	
	printf("通信结束!\n");
	return 0;
}

信息量:

基本特点:由内核共享维护的一个"全局变量",用于记录共享资源的数量,限制进程对资源的访问

​ 1、如果变量的值大于0,说明可以使用资源,此时需要将变量值-1,然后才能使用资源

​ 2、如果变量的值等于0 ,说明可以资源可以使用,此时进程会进入休眠,直到变量大于0,进程会被唤醒,执行步骤 1

​ 3、当资源使用完毕时,变量的值+1,正在休眠的进程可以被唤醒了

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建或获取信息量
key:IPC键值
nsems:信号量的数量 一般写1即可
semflg:
	IPC_CREAT	创建信号量
	IPC_EXCL	存在则返回-1
	mode:		权限
返回值:IPC标识符	失败返回-1

int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对信号量进行加减操作
semid:IPC标识符
sops:
		struct sembuf
		{
           unsigned short sem_num;  //	信号量下标
           short  sem_op;   		
           			1		信号量+1
           			0		等待信号量的值为0
           		   >0		信号量-1,如果不能-1,则阻塞
           short  sem_flg; 
           			IPC_NOWAIT	不阻塞
           			SEM_UNDO	如果进程终止还没还原信号量,系统将自动还原
		}
nsops:表示sops指针指向多少个结构体,一般写1

int semctl(int semid, int semnum, int cmd, ...);
功能:删除/控制信号量
semid:IPC标识符
semnum:信号量下标
cmd:
	IPC_STAT	获取共享内存属性	
	IPC_SET		设置共享内存属性	
	IPC_RMID	删除共享内存		 
	GETALL		获取所有信号量的值
    GETVAL		获取某个信号量的值
    GETNCNT		获取所有等待减信号的进程数量
    SETALL		设置所有信号量的值
    SETVAL		设置某个信号量的值
   union semun {
           int              val;    //	用于设置信号量的值
           struct semid_ds *buf;    //	信号量的属性
           unsigned short  *array;  //	批量获取/设置信号量的值  
   };

骑小毛驴

#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>

int main(int argc,const char* argv[])
{
	//	创建信号量
	int semid = semget(ftok(".",119),1,IPC_CREAT|0644);
	if(0 > semid)
	{
		perror("semget");
		return -1;
	}

	//	设置信号量的值
	if(semctl(semid,0,SETVAL,5))
	{
		perror("semctl");
		return -1;
	}
//	printf("%d\n",semctl(semid,0,GETVAL));
	printf("我是进程%u,我有%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));


	//	十个进程争夺五个资源
	for(int i=0; i<10; i++)
	{
		pid_t pid = fork();
		if(0 == pid)
		{
			struct sembuf buf = {0,-1,0};
			semop(semid,&buf,1);
			printf("我是子进程%u,我骑了一头小毛驴,还有%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
			sleep(i);
			buf.sem_op = 1;
			semop(semid,&buf,1);
			printf("我是子进程%u,我还了一头小毛驴,还有%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
			return 0;
		}
	}

	while(-1 !=wait(NULL));

	printf("我是进程%u,我有%d头小毛驴\n",getpid(),semctl(semid,0,GETVAL));
	if(semctl(semid,0,IPC_RMID))
	{
		perror("semctl");
		return -1;
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值