进程线程(wait与exec族函数、同步与互斥、管道、信号量、共享内存、消息队列)笔记-day13

目录

前言

一、Linux多任务

1.1定义、特性、类别

1.2Linux进程命令

二、多进程

2.1声明、状态

2.2fork()函数、父子进程

2.3 -exit()、exit()、getpid()、getppid()

2.4 孤儿、僵尸、守护进程

2.5 wait族函数、exec函数族

三、多线程

3.1创建、函数详解 

3.2动/静态库、静/动态链接

3.3初始化及阻塞函数

3.4 互斥锁、信号量

四、进程通信

4.1管道(有名管道和无名管道)

4.2信号

4.3信号量

4.4共享内存

4.5消息队列

总结


前言

这个星期将进入进程与线程,了解父子进程、孤儿进程及僵尸进程、fork()函数;进程线程的创建、阻塞等待及wait与exec族函数;进程间通信的6种方式(管道、信号、信号量、共享内存、消息队列及socket套接字);共享库与静态库、静态链接与动态链接等


提示:以下是本篇文章正文内容,下面案例可供参考

一、Linux多任务

*************************************************************************************
linux多任务机制介绍:
	(1)多任务处理
		指用户可以在同一时间内运行多个应用程序,每个正在执行的应用程序被称为一个任务
	(2) 任务的执行
		一次任务的执行,可以迸发激活多个进程,这些进程相互合作来完成这个终极目标
	(3)多任务执行的本质
		      处理器在某一时刻只能执行一个任务,每个任务创建时被分配时间片,任务执行(占用时间片),时间片递减
		操作系统的时间片用完时调度执行其他的任务,频繁切换并且时间非常短,因此给用户的感觉像是多个任务同时运行
		
		多任务处理不是绝对意义上的同时运行,是相对意义上的同时运行,因为cpu处理速度快,频繁切换 

进程和程序的区别 
	程序: 静态的, 保存在磁盘上有序指令的集合
	进程: 动态的, 是一个程序的执行的过程,进程是有生命周期的(创建,运行,消亡)
	
	进程是资源分配的最小单位 
**************************************************************************************

1.1定义、特性、类别

  (1)进程是一个程序的一次执行的过程	./a.out  //敲回车之后,进程从 创建  运行 消亡
		while(1)//永远活着,运行
		{
			printf("hello world!!\n");
			sleep(1);
		}
	(2)进程是一个独立的可调度的任务
	(3)进程是资源分配的最小单位
	(4)进程和程序的区别
		程序: .c文件就是一个源程序,a.out 可执行程序 静态的,保存在磁盘上的有序指令的集合
    进程: ./a.out 运行程序,动态的,进程是一个程序的执行过程,是有生命周期的 
    从 创建 运行 消亡( 代码区 常量区 全局区 堆 栈 )
	(5)进程的特性
		并发性、动态性、交互性、独立性
	   
		并发性 //多个任务同时执行
		动态性 //是有生命周期的 从 创建 运行 消亡( 代码区 常量区 全局区 堆 栈 )
		交互性 //scanf printf 输入输出
		独立性 //每个进程都有自己独立的4G虚拟空间
	  
	(6)进程分类:交互式进程、批处理进程、守护进程

1.2Linux进程命令

  2.查看所有进程命令:ps aux 查看所有进程的相关信息	
           top 动态的显示进程的相关信息,每隔几s更新一次,按q退出

  3.进程关系树(init、PID) pstree 列出进程关系树,1号进程 init	

  4.在程序中如何获取当前进程的pid(getpid()和getppid())
每个进程讲的运行,都有一个自己的ID,叫PID

typedef int pid_t;

#include 
#include 
pid_t getpid(void)  功能: 获取当前进程PID,也就是身份证号
                    返回值 当前进程的PID 

pid_t getppid(void);  多出的p是缩写 parent 
                    功能: 获取当前进程父进程的PID, 也就他父亲的身份证号
                    返回值:当前进程父进程的PID 	
					
************代码演示(getpid  getppid)**************

#include "my.h"

int main(int argc, const char *argv[])
{
	printf("当前进程的PID是%d\n",getpid());
	printf("当前进程父进程的PID是%d\n",getppid());
	return 0;
}

//思考,多次运行,为什么父进程的PID没有变化??? 

	运行在终端上的进程,他的父进程是终端
	父进程的PID是2629
	kill -9 2629 杀死终端这个进程  -9是必杀信号
***********************************************************************************

二、多进程

2.1声明、状态

5. 进程说明
 (1) pid号的顺序:顺序增加		
 (2) pid的回收:每个进程运行结束后,系统会回收进程ID(PID),但不会立即使用
 (3) 进程与终端:所有在终端上运行的进程其父进程都为终端,终端结束,其终端上的进程也结束

6. 进程状态(就绪态 运行态 阻塞态见下附图)

进程状态(就绪态 运行态 阻塞态见附图)

进程线程(wait与exec族函数、同步与互斥、管道、信号量、共享内存、消息队列)笔记-day13_第1张图片

 

2.2fork()函数、父子进程

7. fork()函数介绍
	#include 
    pid_t fork(void);
	pid_t 类型就是 int  //define int pid_t

   功能:用于创建一个子进程,当执行完fork 就会出现一个新进程(新进程称为子进程,原来的进程称为父进程)		
fork执行完 子进程会几乎复制 父进程所有东西,并且从fork后面语句开始执行,调用一次,返回两次		

		**********代码演示***********		
#include "my.h"

int main(int argc, const char *argv[])
{
	fork();//只要执行fork函数,会立刻创建一个子进程,且子进程从fork函数的下一行开始执行
	printf("hello world!!\n");
	return 0;
}		
        **********代码运行结果***********	
inux@ubuntu:~/22021班/0413$ ./a.out 
hello world!!
linux@ubuntu:~/22021班/0413$ hello world!!
//打印输出两遍hello world,是因为 父进程打印一遍,子进程打印一遍				


//思考,哪个是父进程打印的,哪个是子进程打印的
	我们需要通过fork函数的返回值来区分父子进程 
	
	 pid_t fork(void);//我们需要通过fork函数的返回值来区分父子进程 
	 
	 pid_t ret = fork();
	 if(ret == -1)
	 {
		 perror("fork failed");//创建子进程失败
		 return -1;
	 }
	 else if(ret > 0)//说明当前进程是父进程  ret 的具体值 是几??? 是子进程的PID
	 {
		 
	 }
	 else if(ret == 0)//说明当前进程是子进程
	 {
		 
	 }				   
	 
        **********代码运行结果***********
parent: 父进程的PID 3620 父进程的父进程PID 3217
hello world!!
linux@ubuntu:~/22021班/0413$ child: 子进程的PID 3621 子进程的父进程的PID 1
hello world!!

3217 终端  3620 ./a.out 父进程  3621 子进程  1  init 

********************************练习***********************************************
创建一个子进程,父子进程同时在运行,死循环,每隔1s 打印一次;实现
父进程打印"parent running!" 间隔1s	子进程打印"child running !" 间隔1s;父子并行执行(同时执行)


#include "my.h"

int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid == 0)
	{
		while(1)//子进程做的事
		{
			printf("child running!!\n");
			sleep(1);
		}
	}
	else if(pid > 0)
	{
		while(1)//父进程做的事
		{
			printf("parent running!!\n");
			sleep(1);
		}
	}
	return 0;
}	

**********count代码演示***********

#include "my.h"

int main(int argc, const char *argv[])
{
	int count = 0;
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid == 0)//子进程
	{
		count++;
	}
	else if(pid > 0)//父进程
	{
		sleep(1);//延时1s,子进程先结束
	}
	printf("count is %d\n",count);
	return 0;
}

#结论:父进程创建子进程,子进程会copy父进程资源,然后两个进程空间完全独立,
子进程某个变量改变,父进程的不变;父子进程的栈区里面,各有一个count变量

count is 1 //子进程先打印,子进程栈区里面的count++,值改变了 
count is 0 //父进程后打印,父进程栈区里面的count值,还是0,不变

如何保证子进程先运行?
	父进程里面放个sleep(1) , cpu不可能等待处理父进程1s
***********************************************************************************

2.3 -exit()、exit()、getpid()、getppid()

************************************************************************************
8. _exit和exit的区别 //exit(0)  > return  >  break  > continue 
	8.1 exit()函数
	(1) 功能:
		exit(0);  //执行exit()函数后,会刷新缓存区,将缓存区中的内容写入到文件中	
		用于结束进程,当程序执行到exit函数时,整个程序就结束了 		
		在使用exit的时候,关于exit的参数,是一个int,代表进程结束时的状态
		我们通常 这样,程序正常结束  exit(0); //0代表正常结束 
									 exit(-1);//-1代表异常结束	
	(2)头文件与函数原型
	#include 
	void exit(int status); 
	#功能:就是直接结束程序,刷新缓存区
	
	(3)参数:
		status是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束,
		其它值表示出现了错误,进程非正常结束
	
	**********代码演示***********
#include "my.h"
int main(int argc, const char *argv[])
{
	printf("hello world!!");//注意此处没有\n
	exit(0);//结束整个程序,结束整个程序前,刷新缓存区
	return 0;//在main函数中,执行return 0,正常结束main函数,也会刷新缓存区
}	

//打印输出hello world //因为执行完exit(0);函数,结束程序,会刷新缓存区
	8.2 _exit()函数
	(1) 功能:用于结束进程,当程序执行到_exit函数时,
		_exit(0);   //当执行此函数时,不刷新缓存区,直接结束程序
	(2)头文件与函数原型
	
	#include 
	void _exit(int status);   // status结束状态,0,表示正常结束
	
	**********代码演示***********
#include "my.h"

int main(int argc, const char *argv[])
{
	printf("hello world!!");//注意此处没有\n
	_exit(0);//结束整个程序,不刷新缓存区
	return 0;//在main函数中,执行return 0,正常结束main函数,也会刷新缓存区
}	
//不会打印输出 hello world ,因为执行完_exit函数不会刷新缓存区,所以hello world没有打印	
	
	
exit函数和_exit函数总结:
	exit函数结束进程前刷新缓存区; _exit函数,结束进程前不刷新缓存区 
	
	其他的功能一样,都是用来结束进程的	参数写0,  表示正常结束 ;参数写-1, 表示异常结束

2.4 孤儿、僵尸、守护进程

父进程不应该比子进程先结束,父进程要负责回收子进程的资源(子进程的PID)
/
孤儿进程:父进程先结束,子进程未结束(孤儿),此时的子进程会被1号进程(init福利院接管),此时的子进程就称为孤儿进程
	
**********代码演示***********

#include "my.h"

int main(int argc, const char *argv[])
{
	//如何形成一个孤儿进程
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid == 0)//子进程
	{
		//子进程活着
		while(1)
		{
			printf("子进程在开心玩着.......\n");
			printf("子进程的父进程的PID是%d\n",getppid());
			sleep(1);
		}
	}
	else if(pid > 0)//父进程
	{
		//让父进程活着3s后结束
		printf("父进程的PID%d\n",getpid());
		sleep(3);
		exit(0);//父进程结束
	}
	return 0;
}	

//父进程活着的时候,子进程不是孤儿进程,3s之后,父进程结束了,子进程被1号init福利院接管,子进程的父进程就是1
ctrl + alt + t //开启新的终端,不在当前目录文件,会从工作目录下打开
ctrl + shift + n //开启新的终端,在当前目录文件夹下,重新打开一个终端

ps aux | grep a.out
/	
	僵尸进程:子进程先结束,父进程未结束,但是父进程没有调用wait族函数,来回收子进程的资源(子进程的PID),那么此时的子进程称为僵尸进程
	
**********代码演示***********
	
#include "my.h"

int main(int argc, const char *argv[])
{
	//如何形成一个僵尸进程
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid == 0)//子进程
	{
		printf("我是子进程,我的PID是%d,我跪了,走远了!!!\n",getpid());
		//子进程先结束
		exit(0);
	}
	else if(pid > 0)//父进程
	{
		//父进程未结束,但又未调用wait族函数,对子进程回收资源
		while(1)
		{
			printf("父进程,开心的打麻将.......\n");
			sleep(1);
		}
	}
	return 0;
}

//僵尸进程多了,是好事还是坏事??? 坏事,我们要避免出现僵尸进程 	
ctrl + alt + t //开启新的终端
ctrl + shift + n //开启新的终端
ps aux | grep a.out 
//如何让子进程先运行;父进程放个sleep(1),可以让子进程先被cpu轮转到

/
守护进程 (daemon)进程

	linux中有两类进程
	(1)前台进程:前台进程占用终端  (2)后台进程:后台进程不占用终端
	如何查看守护进程命令	 ps -axj

    创建守护进程的过程,需要大家背下来
	编写守护进程需要5步
	(1)创建一个子进程,结束父进程(让其称为孤儿进程)
	(2)子进程创建新会话(setsid()函数) //创建新的会话就是为了脱离控制终端和进程组
			多个进程 在一起组成 进程组 
			多个进程组 在一起组成 会话
	(3)修改进程当前目录(chdir("/tmp"))//不是必须的change directory
	(4)重置文件权限掩码(umask(0))//不是必须的, umask(0) ,没有文件权限掩码
		命令 umask //文件权限掩码 
	(5)关闭子进程从父进程复制过来的文件描述符
		int num = getdtablesize(); //获取文件描述符表大小 get fd table size
		int i;
		for(i = 0; i < num; i++)
		{
			close(i);
		}
//文件描述符从3开始,0、1、2已经被标准输入、标准输出、标准错误输出占用	
#################练习3#################	
  创建一个守护进程,实现每隔2秒,向mydaemon.txt文件中写入系统时间

#include "my.h"

int main(int argc, const char *argv[])
{
	//如何形成一个僵尸进程
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid > 0)//父进程
	{
		//1.创建一个子进程后,先结束父进程,形成一个孤儿进程
		exit(0);
	}
	else if(pid == 0)//子进程
	{
		//2.创建新的会话,让当前子进程脱离原来的控制终端和进程组
		setsid();
		//3.修改进程的当前目录,此步骤,不是必须的
		chdir("/tmp");
		//4.重置文件权限掩码,此步骤,不是必须的 
		umask(0);//没有掩码
		//5.关闭子进程从父进程拷贝过来的文件描述符
		int i;
		time_t raw_time;
		char* p = NULL;
		int num = getdtablesize();//获取当前文件描述符表的大小
		for(i = 0; i < num; i++)
			close(i);
		//守护进程要干的事,死循环,每隔2s,向 mydaemon.txt文件中写入一个系统时间
		int fd = open("./mydaemon.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
		if(fd == -1)
		{
			perror("open failed");
			exit(-1);
		}
		while(1)
		{
			time(&raw_time);//得到秒数
			p = ctime(&raw_time);//转为英文格式时间
			write(fd,p,strlen(p));
			sleep(2);
		}
	}
	return 0;
}

///杀死守护进程 ps aux | grep a.out   kill -9 2341 

2.5 wait族函数、exec函数族

wait族函数 //专门是用来回收子进程的资源的
  (1)如何回收子进程的资源
		wait();
		waitpid();
	(2)wait()函数
		1)功能:wait(当父进程执行此函数时,父进程 阻塞 等待子进程结束)
	父进程执行到wait函数就会阻塞,等待子进程的结束,一旦有一个子进程结束,wait将结束阻塞
	当子进程结束时,父进程会通过wait函数回收子进程的资源
	
		2)头文件及函数原型		
		#include 
        #include 

        pid_t wait(int *status);
		3)函数参数
		status是一个整型指针,指向的对象用来保存子进程退出时的状态。
 		status若为空,表示忽略子进程退出时的状态
 		status若不为空,表示保存子进程退出时的状态
		//调用的时候 wait(NULL);// 阻塞等待子进程结束 + 回收子进程的资源
		
		4)函数返回值
		成功:pid_t //返回值是结束的那个子进程的pid		失败:-1

	/对获取子进程结束状态的验证
status   //输出参数,里面保存的是子进程结束时的状态
pid_t    //返回值是结束的那个子进程的id

**********代码演示***********

//子进程延时仍无效
//对阻塞功能和返回值的验证(延时无效)
#include "my.h"

int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid == 0)//子进程
	{
		//在子进程里面加一个延时2s,想让父进程先被cpu处理,结束掉,但是没有实现,因为父进程中有wait函数,阻塞等待子进程的结束
		sleep(2);
		printf("child:子进程跪了!!!\n");
		exit(0);
	}
	else if(pid > 0)//父进程
	{
		wait(NULL);//阻塞等待子进程的结束,子进程一旦结束,wait函数立刻解除阻塞,并回收子进程资源
		printf("parent: wait函数结束阻塞,并且已经回收子进程资源完毕!!\n");
	}
	return 0;
}

##################################################################################

	(2)waitpid()函数	
		1)功能:指定等某个待子进程的结束
		2)头文件及函数原型:
		
		#include 
		#include 
		pid_t wait(int *status);
		pid_t waitpid(pid_t pid, int *status, int options);//等待指定的子进程结束
			
		3)参数说明:
		pid_t pid //pid > 0 回收进程的ID 	
		int *status //同wait
		options: //选择 你可以选择,让waitpid函数是阻塞的函数,也可以选择让waitpid函数是非阻塞的函数  
		WNOHANG 表示不阻塞, W wait NO no  HANG 阻塞  waitpid不阻塞而立即返回,此时值为0,结束后返回值为结束子进程pid
			0       阻塞					
		第三个参数 代表 让waitpid函数 是阻塞函数,还是非阻塞函数
			//阻塞 
			waitpid(1111, NULL, 0); //阻塞等待PID是1111的子进程结束
			//非阻塞 
			waitpid(1111, NULL, WNOHANG); //非阻塞等待PID是1111的子进程结束
	
    	4)返回值:
			>0: 已经结束运行的子进程进程号		
			0: 如果子进程没有结束,waitpid(1111, NULL, WNOHANG);函数的返回值是 0,当子进程结束的时候
				waitpid(1111, NULL, WNOHANG);函数的返回值 >0,并且这个值,就是结束的那个子进程的PID		
			-1:出错
			
		5)应用举例
		waitpid(31111, NULL, 0);   //指定阻塞等待PID 31111 子进程结束
		
waitpid函数阻塞功能举例//
#include "my.h"
int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid == 0)//子进程
	{
		//在子进程里面加一个延时2s,想让父进程先被cpu处理,结束掉,但是并没有实现,因为父进程有waitpid函数阻塞等待子进程结束,收尸
		sleep(2);
		printf("child:子进程跪了!!!\n");
		exit(0);
	}
	else if(pid > 0)//父进程,fork函数的返回值>0,返回值具体是几??就是子进程的PID,所以放在waitpid函数的第一个参数上,刚好合适
	{
		//第一个参数pid变量里面,存储的就是子进程的pid
		waitpid(pid,NULL,0);//第三个参数0,代表阻塞等待指定的子进程结束
		printf("parent: waitpid函数结束阻塞,并且已经回收子进程资源完毕!!\n");
	}
	return 0;
}
		
//waitpid函数非阻塞功能举例///
     **********代码演示***********	
#include 
#include 
#include 
#include 
int main()
{
	pid_t pid;
	pid = fork();
	if(pid == 0)
	{
		sleep(5);
		exit(0);
	}
	else if(pid > 0)
	{
		while(1)	//循环测试子进程是否退出
		{
			int ret = waitpid(pid,NULL,WNOHANG);	//调用waitpid,且父进程不阻塞
			if(ret == 0)//waitpid函数的返回值是0,说明子进程没有结束,1s之后,继续判断子进程是否结束
			{
				puts("child is not exited!!");	//若子进程未退出,则父进程暂停1s,1s之后,再次判断子进程是否结束
				sleep(1);
			}
			else if(pid == ret)	//若发现子进程退出,waitpid函数的返回值,就是结束的那个子进程的PID,打印输出相应情况
			{
				printf("child is exited!!\n");
				break;
			}
			else
			{
				printf("some error occured!!\n");
				break;
			}
		}
	}
	return 0;
}

************************************************************************************
exec函数族(通过调用下面的函数可以执行另外一个程序)

 (1)功能: //exec族函数,通常在子进程中调用
用fork函数创建子进程后,子进程往往要调用一种exec族函数以执行另一个程序。
当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。
因为调用exec并不创建新进程,所以前后的进程PID并未改变。exec只是用另一个新程序替换了
当前进程的正文、数据、堆和栈段。
	
	exec 	 a.out  你 mkdir  他 a.out  ------> mkdir(程序,命令也是一个程序)   
	execl
	execv
	execle
	execve
	execlp
	ececvp

(2)头文件及函数原型
	#include 
	int execl(const char *path, const char *arg, ...);
	int execlp(const char *file, const char *arg, ...); //...表示多个参数
	int execle(const char *path, const char *arg,
		  ..., char * const envp[]);
	int execv(const char *path, char *const argv[]);
	int execvp(const char *file, char *const argv[]);
	int execvpe(const char *file, char *const argv[],
		  char *const envp[]);
		  
l //list 缩写 希望接收以逗号分隔的参数列表,列表以NULL作为结尾标志
p //PATH 系统会按照PATH环境变量进行路径进行查找命令		  
e //enviroment 缩写 指定当前进程所使用的环境变量
v //vertor 缩写 参数传递为以NULL结尾的字符指针数组

const char *path 
六个函数返回:若出错则为- 1,若成功则不返回

**********代码演示***********
int execlp(char * file, char *arg, ...);
execlp("mkdir","mkdir","haha",NULL)

参数说明:

char * file  //去执行新的程序的名称 
char *arg, ...  //在命令行上执行这个程序的过程 , 最后一个NULL代表参数结束

返回值:出错返回-1
		成功不返回
//注意 : exec函数族, 一个进程中只会执行一次, 执行成功,原进程退出--新进程开始运行

**********代码演示***********
#include "my.h"

int main(int argc, const char *argv[])
{
	//调用execlp函数,会去执行的程序mkdir,执行mkdir程序,会在当前目录下创建一个haha目录
	execlp("mkdir","mkdir","haha",NULL);
	printf("1111111111\n");//为什么111111111没有被打印,因为调用execlp函数后,去执行的新的
	//程序,当前的程序 代码段 数据段 堆 栈被新的程序mkdir,替换,从mkdir程序的main开始执行新的程序
	return 0;
}

//execlp 经常与多进程组合使用,用一个子进程单独执行execlp程序
#include "my.h"
int main(int argc, const char *argv[])
{
	pid_t ret = fork();
	if(ret == -1)
	{
		perror("fork failed");
		exit(-1);
	}
	else if(ret > 0)
	{
		wait(NULL);//阻塞等待子进程结束
		printf("父进程,正常的活着!!\n");
	}
	else if(ret == 0)
	{
		printf("子进程即将被换血了!!\n");
		execlp("ls","ls","/home/linux",NULL);
	}
	return 0;
}

#################练习1#################
用一个子进程执行execlp函数(ls -a /home/linux),父进程中打印子进程ID

#include "my.h"

int main(int argc, const char *argv[])
{
	pid_t ret = fork();
	if(ret == -1)
	{
		perror("fork failed");
		exit(-1);
	}
	else if(ret > 0)
	{
		wait(NULL);
		printf("子进程的PID是%d\n",ret);
	}
	else if(ret == 0)
	{
		printf("子进程即将被换血了!!\n");
		execlp("ls","ls", "-a", "/home/linux",NULL);
	}
	return 0;
}

#################练习2#################

  完成下列程序,调用系统的cp指令,实现文件copy功能。用execlp()函数
  当文件copy完成后系统打印"copy over", 文件名通过参数传递
  ./a.out a.c b.c             //  ./a.out  a.c b.c        ///cp a.c b.c
  

 #include "my.h"

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("忘记传递参数了!! ./a.out a.c b.c\n");
		exit(-1);
	}
	pid_t ret = fork();
	if(ret == -1)
	{
		perror("fork failed");
		exit(-1);
	}
	else if(ret > 0)
	{
		wait(NULL);
		printf("copy over!!\n");
	}
	else if(ret == 0)
	{
		printf("子进程即将被换血了!!\n");
		execlp("cp","cp", argv[1], argv[2],NULL);//注意此处的argv[1] 和 argv[2]不要加" "号
	}
	return 0;
}

三、多线程

3.1创建、函数详解 

1.线程
(1) 线程是轻量级进程
(2) 线程依附于进程(1个进程可以创建多个线程,如果进程结束,进程创建的线程也结束)
	皮之不存,毛将焉附
(3) 线程不能独立存在,进程结束,线程也结束
(4) 多线程只新开辟栈区,所以线程的执行效率,高于进程
(5) 线程是独立运行的最小单位;进程是资源分配的最小单位

2.在进程中创建线程
   头文件和函数原型
	#include 
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
					  void *(*start_routine) (void *), void *arg);
	(1)功能:创建一个线程  
	(2)参数说明:
		pthread_t id; //用来保存线程的ID 
		pthread_create(&id, NULL, ,NULL);
		pthread_t *thread //用来保存线程的id
		
		const pthread_attr_t *attr //线程的属性 ,通常为NULL
		void *(*p) (void *)  //第三个,参数非常重要,就是线程执行的功能函数
		
		//p的类型 函数指针 void* (*) (void*)
		//p指向的函数类型: void* (void*)
		
		void *arg //第四个参数是为第三个参数服务,给三个参数,调用函数的时候,
		           //传参用的(给线程函数传递参数用的)
	(3)返回值 成功: 0  失败:-1
	(4) 实例 
		void *fun(void *p) //线程执行的函数,线程
		{
			;
		}
		pthread_t id;
		pthread_create(&id,NULL,fun,NULL);

创建一个线程实例
///my.h
#ifndef _MY_H
#define _MY_H
//如何避免头文件的重复包含
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#endif
	
*********************代码演示***********************
#include "my.h"


//线程的功能函数,fun1的参数个数和参数类型是固定 参数void*,返回值void*
void* fun1(void* p)
{
	while(1)//线程干的事,每个1s,打印一次hello world
	{
		printf("hello world!!\n");
		sleep(1);
	}
}

int main(int argc, const char *argv[])
{
	pthread_t id;//用来保存线程的ID
	//创建一个线程
	pthread_create(&id, NULL,fun1,NULL);//程序自动取执行fun1函数
	printf("111111111111111111\n");
	//sleep(3);//进程活了3s,那么线程就能活3s
	//阻塞函数
	getchar();//一直等待输入,如果,一直不输入,一直阻塞,主进程一直活着
	return 0;
}


//注意编译的时候,要加上 -lpthread参数 
gcc 14-test.c -lpthread

//编译多线程程序注意的地方//gcc hello.c -lpthread
// -l lib pthread pthread.so 

3.2动/静态库、静/动态链接

*****静态库和共享库的区别******
(1)静态库(静态链接):编译时会将将静态库链接到目标代码中,运行时不在需要静态库,因此体积大
(2)共享库(动态链接):编译时不会将共享库链接到目标代码中,运行时需要加载动态库,因此体积小		 

静态链接: 编译的时候找函数,函数源码编译到程序中 
动态链接: 运行的时候找函数,程序只有函数的地址

-lpthread     libpthread.so 是一个第三方库文件, -lpthread 表示加载第三方库libpthread.so

静态链接:编译时找函数,函数被放在可执行文件中       动态链接:运行时找函数

区别:    1 可执行文件大小的角度(静态链接大) 
		 2 从占用内存的角度(静态占用内存多)
		 3 从效率角度来说(静态方式效率低)
		 4 从升级的角度(动态更容易升级)
		 5 安装的角度(动态库有多个文件,如果有一个被误删除,文件执行不了, 静态的是可执行文件和库是一体的)	
		 
gcc pthread_create.c -lpthread

3.3初始化及阻塞函数

wait(NULL);//阻塞等待子进程的结束,一但子进程结束,wait立刻解除阻塞,并回收资源
3. 如何阻塞等待一个子线程的结束

pthread_join()函数	功能:用来阻塞等待指定线程结束

	3.1 头文件及函数原型
	#include 
    int pthread_join(pthread_t thread, void **retval);
	
	(1)参数说明:		
		pthread_t thread //阻塞等待线程结束的id
		void **retval //用来保存线程结束时的返回值
		
	(2)返回值:	成功: 0	失败: -1
	
*********************代码演示***********************
#include "my.h"

void* thread1(void* p)
{
	while(1)
	{
		printf("1 is running!!\n");
		sleep(1);
	}
}

int main(int argc, const char *argv[])
{
	pthread_t id1, id2;
	pthread_create(&id1, NULL,thread1,NULL);//初始化线程
	pthread_join(id1, NULL);//阻塞等待线程1结束,直到线程1结束,pthread_join函数才会解除阻塞
	return 0;
}	


  3.2 线程参数
//实例1:传递字符串给线程/
#include "my.h"

void* thread1(void* p)//创建线程之后,参数p里面保存的就是数组name的首地址
{
	//p是无类型指针,不能够直接使用
	//使用方法一,可以先将无类型指针赋值给有类型的指针,再使用有类型的指针
	char* q = p;
	//使用方法二,可以直接将无类型的指针强制类型转换为有类型的指针,再使用
	while(1)
	{
		//%s,打印字符串,只需要字符串的首地址,就可以将字符串打印输出
		printf("方法1:%s\n",q);
		printf("方法2:%s\n",(char*)p);
		sleep(1);
	}
}

int main(int argc, const char *argv[])
{
	char name[20] = "asan";
	//main函数中有个字符串,如果将这个字符串的首地址传递给线程1,然后在线程1里面,将hello world!!打印
	pthread_t id1;
	pthread_create(&id1, NULL,thread1, name);
	pthread_join(id1, NULL);//阻塞等待线程1结束,直到线程1结束,pthread_join函数才会解除阻塞
	return 0;
}

//实例2:传递整型变量给线程/
#include "my.h"

void* thread1(void* p)//执行线程的是p里面保存的就是&age
{
	//p是无类型指针,不能够直接使用
	//使用方法一,可以先将无类型指针赋值给有类型的指针
	int* q = p;
	//使用方法二,可以直接将无类型的指针强制类型转换为有类型的指针,再使用
	int age = *((int*)p);//先将p强制类型转换为有类型的指针变量,在加个*取值,找到main函数中的age
	while(1)
	{
		
		printf("方法1: %d\n",*q);
		printf("方法2: %d\n",age);
		sleep(1);
	}
}
int main(int argc, const char *argv[])
{
	int age = 19;
	//如何将main函数中的age变量的首地址,传递给线程,在线程中打印年龄
	pthread_t id1;
	pthread_create(&id1, NULL,thread1, &age);
	pthread_join(id1, NULL);//阻塞等待线程1结束,直到线程1结束,pthread_join函数才会解除阻塞
	return 0;
}

//实例3:传递结构体变量给线程/
##########练习############
创建一个线程,主程序给线程传递一个结构体student,student结构体有两个成员: 姓名,  年龄
线程不断打印student的姓名和年龄信息

#include "my.h"

struct student   //定义结构体
{
	char name[20];
	int age;
};

void* thread1(void* p)//执行线程后,p里面保存的就是结构体变量s的首地址
{
	//p是无类型指针,不能够直接使用
	//使用方法一,可以先将无类型指针赋值给有类型的指针
	struct student* q = p;
	//使用方法二,可以直接将无类型的指针强制类型转换为有类型的指针,再使用
	while(1)
	{
		//结构体指针访问成员变量用->
		printf("方法1: %s %d\n",q->name,q->age);
		printf("方法2: %s %d\n",((struct student*)p)->name, ((struct student*)p)->age);
		// (struct student*)p 先强制转换为有类型的指针,再访问成员变量 
		// 因为优先级的关系 在 (struct student*)p 的外面再加个() 再访问成员变量
		// ((struct student*)p)->name
		sleep(1);
	}
}

int main(int argc, const char *argv[])
{
	char name[20] = "asan";
	int age = 19;
	//定义一个结构体,将二者装到一个结构体里面
	struct student s;
	//将name数组和age赋值给结构体变量,然后打包,通过线程的第4个参数,传递给线程1
	strcpy(s.name,name);
	s.age = age;

	pthread_t id1;
	pthread_create(&id1, NULL,thread1, &s);//将结构体变量s的首地址传递过去

	pthread_join(id1, NULL);//阻塞等待线程1结束,直到线程1结束,pthread_join函数才会解除阻塞
	return 0;
}


  3.3 pthread_exit()函数 
	头文件及函数原型
	#include 
    void pthread_exit(void *retval);

	(1)功能:结束当前所在线程
	(2)参数说明:void *retval//线程结束时返回值
	(4)实例:     pthread_exit(NULL); //执行此语句后,线程结束
//
#include "my.h"

void* thread1(void* p)
{
	int count = 0;
	while(1)
	{
		printf("hello world!!\n");
		count++;
		if(count == 3)
			pthread_exit(NULL); //执行此函数,就会立刻结束当前的线程
		//	break;
		sleep(1);
	}
}
int main(int argc, const char *argv[])
{
	pthread_t id;
	pthread_create(&id, NULL, thread1, NULL);
	pthread_join(id, NULL);//3s之后,线程结束了,pthread_join解除阻塞,程序继续下执行打印111111,然后结束
	printf("111111111111\n");
	return 0;
}
************************************************************************************
pthread_create //创建线程
pthread_join //阻塞等待线程结束
pthread_exit //结束线程

3.4 互斥锁、信号量

4. 线程同步与互斥:
	4.1 线程的互斥
	(1)什么是线程的互斥?	有一个共享资源,A用的时候,其他任何人都不能用	吃独食
*********************互斥代码演示***********************
#include "my.h"
//共享资源 全局变量
int max = 100;

void* thread1(void* p)
{
	while(1)
	{
		max = 50;
		sleep(2);//延时2s,cpu不可能等你2s,肯定会轮转到线程2,将max改0,所以每次睡醒之后都打印00
		printf("max is %d\n",max);
	}
}
void* thread2(void* p)
{
	while(1)
	{
		max = 0;
	}
}

int main(int argc, const char *argv[])
{
	pthread_t id1,id2;
	pthread_create(&id1, NULL, thread1, NULL);
	pthread_create(&id2, NULL, thread2, NULL);
	pthread_join(id1, NULL);
	pthread_join(id2, NULL);
	return 0;
}

//打印的结果 每次都是 max is 0  
//如何解决上面的bug,当线程1,在sleep(2),睡醒之后,max里面依然是50,在睡觉的时候,不让其他人碰max ,解决互斥问题

	(3)互斥锁
		(1)互斥锁的作用:	
			用来解决线程之间共享资源的互斥问题
		(2)互斥锁的作用原理
			线程A在使用共享资源的时候需要先加锁,使用之后解锁 
			线程B在使用共享资源的时候也需要先加锁,如果加锁不成功,阻塞等待状态,直到线程A解锁
	(4)互斥锁使用方法(三步)
		(1)创建一个互斥锁		(2)加锁 		(3)解锁
	————————————————————————————————————————————————————————————————————————————
	第一步:创建互斥锁(互斥锁初始化)、
	(1)头文件及函数原型
	#include 
	int pthread_mutex_init(pthread_mutex_t *mutex,
              const pthread_mutexattr_t *restrict attr);
	
	(2)参数说明:
		pthread_mutex_t *mutex //互斥锁 
		const pthread_mutexattr_t *restrict attr //互斥锁的属性,通常NULL
		pthread_mutex_t m;// 定义一个结构体变量叫m,一个把互斥锁
		
		pthread_mutex_init(&m, NULL);
		
	(3)返回值:	成功:返回0	    失败:返回错误编号
	
	(4)实例  
	pthread_mutex_t mutex;
	pthread_mutex_init(&mutex,NULL);
	——————————————————————————————————————————————————————————————————————————————
	第二歩: 加锁
	(1)头文件及函数原型
	#include 
    int pthread_mutex_lock(pthread_mutex_t *mutex);
	(2)功能:	加锁 
	(3)参数说明:
		pthread_mutex_t *mutex	 //互斥锁  
		pthread_lock(&m);

	(4)返回值:	成功: 0		失败:返回错误编号
	
	(5)实例
		pthread_mutex_lock(&mutex);
	———————————————————————————————————————————————————————————————————————————————
	第三歩:解锁
	功能:解锁 
	(1)头文件及函数原型 
	#include 
	int pthread_mutex_unlock(pthread_mutex_t *mutex);
	(3)参数说明:
		pthread_mutex_t *mutex	 //互斥锁  
		
		pthread_unlock(&m);
	(4)返回值:	成功: 0		失败:返回错误编号
	(5)实例
		pthread_mutex_unlock(&mutex);
	———————————————————————————————————————————————————————————————————————————————
	
*********************代码演示***********************	
//添加互斥锁解决线程的互斥问题
#include "my.h"

pthread_mutex_t m;//定义一个结构体变量m,也就一把互斥锁
int max = 100;//共享资源 全局变量

void* thread1(void* p)
{
	while(1)
	{
		//在访问共享资源前,先加锁,加锁成功,访问共享资源,加锁失败,阻塞等待,直到加锁成功,解除阻塞
		pthread_mutex_lock(&m);//早晚有一天,小A抢到了,锁
		max = 50;
		 sleep(2);
		printf("max is %d\n",max);
		pthread_mutex_unlock(&m);//在使用完共享资源后,一定要解锁
	}
}
void* thread2(void* p)
{
	while(1)
	{
		//在访问共享资源前,先加锁,加锁成功,访问共享资源,加锁失败,阻塞等待,直到加锁成功,解除阻塞
		pthread_mutex_lock(&m); //阻塞
		max = 0;
		pthread_mutex_unlock(&m);//在使用完共享资源后,一定要解锁
	}
}

int main(int argc, const char *argv[])
{
	pthread_t id1,id2;
	pthread_mutex_init(&m, NULL);//初始化互斥所
	pthread_create(&id1, NULL, thread1, NULL);
	pthread_create(&id2, NULL, thread2, NULL);
	pthread_join(id1, NULL);
	pthread_join(id2, NULL);
	return 0;
}

//每次打印的结果是 max is 50 ,因为任意时刻,只能有一个线程加锁成功,解除阻塞,访问共享资源
//剩下的其它线程都是加锁失败,处于阻塞等待状态

	(5)互斥锁总结:
    	1)每个临界资源(共享资源)都由互斥锁来保护,任何时刻最多只能有一个线程能访问该资源
		2)线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,
		  线程会阻塞直到获得锁为止

************************************************************************************
	4.2 线程同步:
		什么是互斥? 多个线程任意时刻只能有一个在使用共享资源
					A用的时候,其他任何人都处于阻塞等待
		什么是同步?
					多个线程按照 一定的  顺序  相互配合来完成任务
	4.2.1 什么是线程的同步?
		同步(synchronization)指的是多个任务(线程),按照约定的 顺序 相互配合完成一件事情
		
		场子,流水线 	线程A(生产啤酒瓶子)---->线程B(装啤酒)---->线程C(盖酒瓶盖子) 
		
	4.2.2 如何解决线程的同步
	信号量  (semaphore 简写   sem)
	(1)什么是信号量? //停车场里面的停车位
		信号量代表某一类资源,其值表示系统中该资源的数量
	(2)信号量的作用
		同样可以解决共享资源互斥和同步的问题     信号量可以控制多个共享资源
	(3)信号量的使用方法(三步)
		1.创建信号量		2.请求信号量 		3.释放信号量

	#incldue 
	sem_t sem; //定义一个结构体变量,名字叫做sem,代表的一个信号量对象
	———————————————————————————————————————————————————————————————————————————————
	第一步:创建信号量(初始化信号量)
	(1)头文件及函数原型 
	#include 
    int sem_init(sem_t *sem, int pshared, unsigned int value);

	(2)参数说明:	
		sem_t *sem //信号量 
		int pshared //通常为0,表示信号量线程之间共享
		unsigned int value //信号量资源的个数,也就是停车位的个数
		
	(3)返回值:		成功: 返回 0		失败: 返回 -1

	(4)实例:
	
	sem_t sem; //定义一个结构体变量,名字叫做sem,代表的一个信号量对象
	sem_init(&sem, 0, 0); //信号量资源的个数为0  解决同步问题
	sem_init(&sem, 0, 1); //信号量资源的个数为1,相当于互斥锁
	
	
	sem_init(&sem, 0, 3); //信号量资源的个数为3
	———————————————————————————————————————————————————————————————————————————————
	第二歩: 请求信号量 : 类似于加锁
	(1)头文件及函数原型
	
	#include 
    int sem_wait(sem_t *sem);
	
	(2)参数说明:sem_t *sem //信号量,一但请求信号成功,共享资源数 -1 (停车位 -1)
	
	(3)返回值:	    成功: 返回 0		失败: 返回 -1

	(4)实例:		sem_wait(&sem);
	———————————————————————————————————————————————————————————————————————————————
	第三歩: 释放信号量 : 类似于解锁
	(1)头文件及函数原型
		#include 
		int sem_post(sem_t *sem);
	
	(2)参数说明:sem_t *sem //信号量 ,如果释放成功,共享资源数 +1 (停车位 +1)
	
	(3)返回值:		成功: 返回 0		失败: 返回 -1

	(4)实例:    	sem_post(&sem);
	———————————————————————————————————————————————————————————————————————————————
	##########练习1############
	//sem_init(&sem, 0, 1);
	//将上面互斥锁的例子改成信号量
	
#include "my.h"

sem_t sem;//定义一个结构体变量sem,也就一个信号量(停车场)
int max = 100;//共享资源 全局变量

void* thread1(void* p)
{
	while(1)
	{
		//在访问共享资源前,请求信号量,如果请求成功,资源数-1(停车位-1),请求失败,阻塞等待
		sem_wait(&sem);//-1
		max = 50;
		sleep(2);
		printf("max is %d\n",max);
		//在使用完共享资源后,一定要释放信号量,资源数+1(停车位+1)
		sem_post(&sem);//+1
	}
}
void* thread2(void* p)
{
	while(1)
	{
		//在访问共享资源前,请求信号量,如果请求成功,资源数-1(停车位-1),请求失败,阻塞等待
		sem_wait(&sem);//-1
		max = 0;
		//在使用完共享资源后,一定要释放信号量,资源数+1(停车位+1)
		sem_post(&sem);//+1
	}
}

int main(int argc, const char *argv[])
{
	pthread_t id1,id2;
	//初始化信号量(停车场,里面停车位的个数)
	sem_init(&sem, 0, 1);//第二个参数0,代表多线程之间共享信号量,第三个参数1,代表资源数有1个(1个停车位),相当于1把互斥锁
	pthread_create(&id1, NULL, thread1, NULL);
	pthread_create(&id2, NULL, thread2, NULL);
	pthread_join(id1, NULL);
	pthread_join(id2, NULL);
	return 0;
}

	##########练习2############
	//sem_init(&sem, 0, 0);    funY线程的运行由funM决定(funM通知funY运行)
	如何保证每次先打印M后打印Y
	M->Y->M->Y->M->Y...........
	
#include "my.h"

sem_t s;//定义一个结构体变量,名字叫s,一个信号量对象(停车场)

void* funM(void* p)
{
	while(1)
	{
		printf("生产啤酒瓶\n");
		//初始化的时候,信号量资源数为0,所以funY一直阻塞等待
		sem_post(&s);//只要调用一次sem_post函数,就可以释放一个信号量,资源数+1,也就是停车位+1
		sleep(1);
	}
}

void* funY(void* p)
{
	while(1)
	{
		//请求信号量
		sem_wait(&s);//请求成功,解除阻塞,被唤醒,请求失败,阻塞等待
		printf("装啤酒\n");
	}
}

int main(int argc, const char *argv[])
{
	pthread_t id1,id2;
	//初始化信号量(初始化停车场的停车位)
	sem_init(&s, 0, 0);//第二个数0,代表的是多线程之间共享信号量,第三个数0,资源数为0(停车位为0)
	pthread_create(&id1, NULL, funM, NULL);
	pthread_create(&id2, NULL, funY, NULL);
	pthread_join(id1,NULL);
	pthread_join(id1,NULL);
	return 0;
}
	
	(4)多线程共享信号量实例
		sem_init(&sem, 0, 3);       
		线程A 调用sem_wait(&sem); //请求信号量,因为个数为3,请求成功,个数 -1 ---> 2
		线程B 调用sem_wait(&sem); //请求信号量,因为个数为2,请求成功,个数 -1 ---> 1
		线程C 调用sem_wait(&sem); //请求信号量,因为个数为1,请求成功,个数 -1 ---> 0
		线程D 调用sem_wait(&sem); //请求信号量,因为个数为0,请求失败,线程D处于阻塞等待状态,
		如果线程A 调用sem_post(&sem); //线程A释放一个信号量 个数 +1 ,线程D可以结束阻塞

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


通过测试 堆栈的大小 大约为8M

pthread_create(pthread_t *thread, pthread_attr_t *attr, void*(*start_routine)(void *), void *arg);
第二个参数 pthread_attr_t *attr;  线程属性,默认NULL

1 获得线程默认属性,赋值给attr
pthread_attr_t attr;
pthread_attr_init(&attr);   // 功能:获取线程的默认属性,会更改attr的值,给一个默认值

2 设置线程的堆栈大小属性
pthread_attr_setstacksize(&attr, 13000000);   ///功能:大小以字节为单位 设置堆栈大小属性


#include "my.h"

void* fun(void* p)
{
	//1 M == 1024kb
	//1 kb == 1024byte
	// 1024*1024字节 = 1M
	// char a[8000000];//大约是8M,小于8M,程序可以正常运行
	char a[9000000];//大约是9M,小于9M,会报段错误,造成栈空间不足
	while(1)
	{
		printf("hello world!!\n");
		sleep(1);
	}
}

int main(int argc, const char *argv[])
{
	pthread_attr_t attr;//用来保存线程的属性
	pthread_t id;
	//1.获取线程属性
	pthread_attr_init(&attr);
	//2.设置堆栈的大小
	pthread_attr_setstacksize(&attr,10000000);//将堆栈设置为大约10M,设置成10M后,上面的运行正常了
	pthread_create(&id, &attr, fun, NULL);
	pthread_join(id,NULL);
	return 0;
}

练习:线程1: 输入用户名 密码  验证用户名 == "user"    密码  == "123456"
    成功: 唤醒线程2  登陆成功   将用户名  密码  登陆时间 存入文件中(user_info.txt)
    失败: 打印 "登陆失败 重新登陆"

typedef struct
{
	char name[20];//用户名
	char passwd[20];//用户密码
}user_info_t;

sem_t sem;//信号量

//登录线程
void* longin(void* arg)//arg = &s;
{
	user_info_t* p = arg;//将无类型赋值给有类型的指针
	while(1)
	{
		printf("请您输入用户名和密码:\n");
		scanf("%s%s",p->name,p->passwd);//用户名和密码,输入保存到了main函数中的结构体变量s中
		
		if(strcmp(p->name,"user") == 0 && strcmp(p->passwd,"123456") == 0)
		{
			printf("登录成功!!\n");
			sem_post(&sem);//释放信号量,唤醒writeInfo线程
			pthread_exit(NULL);//结束登录线程
		}
		else
		{
			printf("登录失败,请您重新输入用户名和密码!!\n");
		}
	}
}

//将用户信息写入文件的线程
void* writeInfo(void* arg)
{
	int fd;
	time_t raw_time;
	char buf[100] = { 0 };
	user_info_t* p = arg;	
	sem_wait(&sem);//最开始处于阻塞等待状态,等待被唤醒
	
	//将用户名 用户密码 用户登录时间写入user_info.txt文件 标准IO和Linux文件IO
	fd = open("./user_info.txt",O_RDWR|O_TRUNC|O_CREAT,0666);
	if(fd == -1)
	{
		perror("open failed");
		return NULL;
	}
	
	time(&raw_time);
	//将 用户名 用户密码 用户登录时间格式化到一个字符串中
	sprintf(buf,"%s %s %s",p->name,p->passwd,ctime(&raw_time));
	//将 用户名 密码 登录时间 组合成一个字符串 存入buf数组
	//再将buf数组,写入到文件中
	write(fd,buf,strlen(buf));
	close(fd);
}

int main(int argc, const char *argv[])
{
	user_info_t s;//最开始结构体里面是随机数
	pthread_t id1,id2;
	sem_init(&sem, 0, 0);//初始化信号量的个数为0,用于解决线程的同步问题
	pthread_create(&id1,NULL,longin,&s);
	pthread_create(&id2,NULL,writeInfo,&s);//保存密码和登录时间的线程,需要登录线程唤醒
	pthread_join(id1,NULL);
	pthread_join(id2,NULL);
	return 0;
}

四、进程通信

    进程间的通信方式(6种)
    
    (1) 管道(有名管道和无名管道)    (2) 信号         (3) 信号量
    (4) 共享内存                     (5) 消息队列     (6) socket通信(网络编程)

4.1管道(有名管道和无名管道)

1. 无名管道(使用的前提条件:只能在亲缘关系进程间使用)
	
	1. 管道有固定的读端和写端,不能即当读端又当写端
	2. 管道是单工通信(半双工通信)
	3. 管道不属于文件系统,存在于内存中,不存在文件指针的概念,不支持lseek移动文件指针操作 ,数据读出后 将从管道移走
	4. 管道 先进先出 FIFO

	1.1创建管道
	
	(1)头文件及函数原型 	
	 #include 
     int pipe(int pipefd[2]); // int pipe(int* pipefd)
	 
	 //调用 
	 int fd[2];//数组有两个元素  fd[0]保存读端文件描述符   fd[1]保存写端文件描述符
	 int ret = pipe(fd);
	 if(ret == -1)
	 {
		 perror("pipe failed");
		 exit(-1);
	 }
	 
	pipe //管道
	(2)功能:创建一个无名管道  
	(3) 参数说明 
	int fd[2];
	fd[0]  保存读端的文件描述符 	fd[1] 保存写端的文件描述符
	
	有了读端和写端的文件描述符,我就可以调用Linux文件IO的read write函数,读取和写入管道文件内容	
	(4)返回值 		成功: 0		失败:-1
	(5)注意:只能用于亲属进程间通信
	
		在一个进程关闭读端 保留写端  在另一个进程关闭写端 保留读端
		写进程在管道尾部写入数据  读进程在管道首部读数据  数据读出后 将从管道移走
			
	******************代码演示********************	  
举例:子进程作为写端,向无名管道中写入一个hello world ,父进程作为读端,从无名管道中读取出内容,打印输出
	
#include "my.h"

int main(int argc, const char *argv[])
{
	//1.创建一个无名管道
	int fd[2];//用来保存读端和写端的文件描述符
	int ret = pipe(fd);
	if(ret == -1)
	{
		perror("pipe failed");
		exit(-1);
	}
	//2.创建一个子进程,父子进程通信
	//子进程作为写端,向无名管道中写入一个hello world ,父进程作为读端,从无名管道中读取出内容,打印输出
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		exit(-1);
	}
	else if(pid == 0)//说明是子进程,作为写端 
	{
		//子进程作为写端,需要先关闭读端
		close(fd[0]);
		char buf[100] = "hello world!!";
		//将字符串写入无名管道
		write(fd[1], buf, sizeof(buf));
	}
	else if(pid > 0)//说明是父进程
	{
		wait(NULL);//子进程写完之后,父进程再读
		//父进程作为读端,需要先关闭写端 
		close(fd[1]);
		char buf[100] = { 0 };
		//将字符串从无名管道中读取出来,保存到buf数组中
		read(fd[0], buf, sizeof(buf));
		printf("read is %s\n",buf);
	}
	return 0;
}		
##########练习1###########
修改上面程序,子进程循环从键盘输入整型数据写入管道,父进程循环读出整型数据,
子进程输入0,结束,父进程读取出0之后,也结束

#include "my.h"

int main(int argc, const char *argv[])
{
	//1.创建一个无名管道
	int fd[2];//用来保存读端和写端的文件描述符
	int ret = pipe(fd);
	if(ret == -1)
	{
		perror("pipe failed");
		exit(-1);
	}
	//2.创建一个子进程,父子进程通信
	//子进程循环输入一个整数,写入无名管道,父进程循环从无名管道中读取名打印
	//子进程每写入一个整数,父进程就立刻读取出来一个,并打印
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		exit(-1);
	}
	else if(pid == 0)//说明是子进程,作为写端 
	{
		int num;
		close(fd[0]);//作为写端,先关闭读端
		while(1)
		{
			printf("Please input num:\n");
			scanf("%d",&num);
			//将num,写入无名管道
			write(fd[1], &num, sizeof(num));
			if(num == 0)
				break;
		}
	}
	else if(pid > 0)//说明是父进程,作为读端
	{
		int num;//从无名管道读取出的数据,存到num中
		close(fd[1]);//作为读端,先关闭写端
		while(1)
		{
			//从无名管道中读取出数据,写入到num中
			read(fd[0], &num, sizeof(num));
			if(num == 0)
				break;
			printf("read is %d\n",num);
		}
	}
	return 0;
}

读端和写端的文件描述,我们并没有调用open函数来的到文件描述符 
//因为无名管道文件,没有名字,所以无法使用open函数来得到读端和写端文件描述
int fdr = open(无名管道名字不知道, O_RDONLY);
int fdw = open(无名管道名字不知道, O_WRONLY);

int fd[2];
pipe(fd);//pipe这个函数,实现了将无名管道的读端和写端的文件描述符,保存到fd[0] 和 fd[1]

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//无名管道只能在有亲缘关系进程之间使用

2. 有名管道(fifo),通常两个不相关的进程间通信(有名管道在有亲无亲缘进程之间都可以使用)
	mkfifo  创建有名管道 (fifo先进先出)	//make  fifo 先进先出
	
   	1. 头文件及函数原型
	 #include 
     #include 
     int mkfifo(const char *pathname, mode_t mode);
	
	(1)功能:创建一个有名管道 
	(2)参数说明:
		int ret = mkfifo("./myfifo",0666);//相对路径的方式,在当前目录下创建一个有名管道文件名字叫myfifo
		int ret = mkfifo("/home/linux/myfifo",0666);
	
		const char *pathname //创建有名管道文件的名字(名字可以随便起) 可以用相对路径也可以用绝对路径
		mode_t mode  //管道文件的权限 
	(3)返回值 	成功: 0		失败: -1
	(4)实例
	mkfifo("./myfifo", 0666); 
	
	- //普通文件 
	d //目录文件 
	c //字符设备文件
	b //块设备文件 
	l //链接文件 
	s //socket套接字文件 
	p //pipe的缩写 管道文件 
	******************代码演示********************
/mkfifo.c 创建一个有名管道myfifo
	
#include "my.h"

int main(int argc, const char *argv[])
{
	//创建一个有名管道,mkfifo函数,需要指定有名管道名字和位置,名字随便起
	int ret = mkfifo("./myfifo",0666);//可以是相对路径也可以是绝对路径
	if(ret == -1)
	{
		perror("mkfifo fialed");
		return -1;
	}
	printf("创建有名管道myfifo成功!!\n");
	return 0;
}	


///继续思考问题???
上面的程序,我们执行了 mkfifo函数,创建了一个有名管道文件myfifo
既然有名管道,有名字,那么我们就可以把有名管道文件 用open函数打开,
进行read 和 write 操作

gcc -o read read.c           ctrl + shift + n //开启新的终端,路径一致 
gcc -o write write.c         alt + table  //切换终端
 
write.c 
//write.c 负责,输入一句话,写入有名管道文件myfifo中
#include "my.h"
int main(int argc, const char *argv[])
{
	char buf[100] = { 0 };
	//1.打开有名管道文件,因为是写端,所以用O_WRONLY
	int fd = open("./myfifo",O_WRONLY);//不可以用O_RDWR,不能既是读又是写,因为单工通信,固定的读端和写端
	if(fd == -1)
	{
		perror("open failed");
		exit(-1);
	}
	//2.输入一个字符串,写入有名管道文件 
	printf("please input string:\n");
	gets(buf);
	write(fd, buf, sizeof(buf));
	return 0;
}
 read.c  
///read.c 负责,从有名管道文件,myfifo中读取出来,并打印,实现进程间的通信
#include "my.h"
int main(int argc, const char *argv[])
{
	char buf[100] = { 0 };//用来保存从有名管道中读取出来的内容
	//1. 打开有名管道, 读端,所以有O_RDONLY
	int fd = open("./myfifo",O_RDONLY);
	if(fd == -1)
	{
		perror("open failed");
		exit(-1);
	}
	//将有名管道的内容读取到buf数组中
	read(fd, buf,sizeof(buf));
	printf("read is %s\n",buf);
	return 0;
}	
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

4.2信号

3. 信号(多进程之间的一种通信方式)
	信号: 进程间通信唯一的异步通信机制 
	异步: 信号什么时候来不知道,自己没有做任何准备  
	互斥: A用的时候,其他所有人都不能用,阻塞等待 
	同步: 多个线程 按照一定的 顺序 配合完成任务 

	3.1 关于信号	
	
	(1)信号使用:通常用于一个进程发送给另一个进程 
	(2)信号发出者:系统、进程 
	(3)信号接受者:进程  
	(4)Ctrl + c   终端识别了ctrl+c,终端进程给运行进程发送了一个信号SIGINT,结束进程
	(5)如何查看系统有哪些信号:
		kill -l
//查看的信号列表,每一个都是宏定义 
#define SIGINT 2
#define SIGQUIT 3
#define SIGALRM 14

		SIGINT 		2  ctrl+c 发送信号 
		SIGQUIT 	3  ctrl+\  终止程序
		SIGILL  	非法指令
		SIGABRT 	通过abort函数实现程序终止,abort函数为异常终止一个进程
		SIGFPE  	除数为0 会产生  --浮点异常
		SIGKILL  	9 必杀信号 
		SIGSEGV    	段错误--地址非法使用 
		SIGPIPE    	管道破裂
		SIGALRM    	14 闹钟信号用alarm函数设置闹钟 告诉系统当时间到 就会发送这个信号 
		SIGTERM   	15 终止信号 kill命令向进程发送这个信号 kill PID 
		SIGCHLD     17 子进程终止 或者停止的时候 会向父进程发送此信号
		SIGCONT    	让一个暂停的进程继续
		SIGSTOP     让一个程序暂停
		SIGTSTP     20 ctrl+z 将前台进程扔到后台 并使该进程处于暂停状态
					
	3.2 信号的发送和捕捉
		
		2.2.1 信号的捕捉
		1、头文件及函数原型
		#include 
		void (*signal(int signum, void (*handler)(int)))(int);
			
		//函数的名字 叫signal	函数的参数列表和函数的返回值类型
		void (*)(int) //返回值类型 
		(int signum, void (*handler)(int)) //参数列表

		
		(1)功能: 捕捉信号函数
		(2)参数:
			int signum //int signum 捕获信号编号
			void (*handler)(int) //函数指针就是捕获到信号后,去执行的函数  函数指针,指向参数为1个int,返回值为void类型的函数
			
			handler //是一个函数指针,类型是void (*)(int) 
			handler 指向的函数类型是 void (int)
			
			void (*p)(int) 
			p的类型,手挡住p, void (*)(int)
			p指向的类型,手挡住(*p)  void (int) 
		(3)返回值	void (*)(int) //返回值类型 
		
		返回值也是一个函数指针,指向参数为一个int 返回值为void类型的函数
		typedef int*  p_t; //将数据类型 int* 重定义为 p_t 
		p_t p; //int* p;
		
		//将函数指针类型重定义 
		typedef void (*)(int) handler_t;
		handler_t signal(int signum, handler_t handler) 
	
	3.3 信号有三种处理方式
	当来了一个信号之后,有三种处理方式 	假设 来了SIGINT 信号 
	(1) 捕获这个信号,去执行一个fun函数,捕获这个信号后,去做的事
		void fun(int num)
		{
			;//捕获到信号后,做什么什么事
		}
		signal(SIGINT, fun);
	(2) 忽略信号 
		signal(SIGINT, SIG_IGN);//ignore  SIG_IGN代表忽略这个信号
		
	(3) 执行默认操作(缺省操作)
		SIGINT信号,默认的操作就是结束终端上的程序
		signal(SIGINT, SIG_DFL);//default 
	///三种方式代码举例/
#include "my.h"

//捕获信号后,执行的函数类型是 void (int)
void fun(int num)
{
	//捕获到的是哪个信号,num的值就是那个信号的编号
	switch(num)
	{
	case SIGINT:
		printf("我不怕 ctrl + c!!\n");
		break;
	case SIGTSTP:
		printf("我是不会停滴!!\n");
		break;
	case SIGTERM:
		printf("想杀我,没门!!\n");
		break;
	}
}

int main(int argc, const char *argv[])
{
	//signal(SIGINT,fun);//捕获SIGINT信号,去执行fun函数
	//signal(SIGINT, SIG_IGN);//忽略SIGINT这个信号
	signal(SIGINT, SIG_DFL);//执行默认操作,缺省操作
	getchar();
	return 0;
}	


++++++++++++++++++++++++++++++++++++++如何捕获信号++++++++++++++++++++++++++++++++++

******************代码演示********************

#include "my.h"

//捕获信号后,执行的函数类型是 void (int)
void fun(int num)
{
	//捕获到的是哪个信号,num的值就是那个信号的编号
	printf("捕获到了信号 num is %d!!\n",num);
}
int main(int argc, const char *argv[])
{
	//调用signal函数可以捕获信号
	signal(SIGINT, fun);//捕获到SIGINT这个信号后,就会立刻执行fun函数 ctrl + c发出SIGINT
	signal(SIGTSTP,fun);//捕获到SIGTSTP这个信号后,就会立刻执行fun函数 ctrl + z 发出SIGTSTP
	signal(SIGTERM,fun);//捕获到SIGTERM这个信号后,就会立刻执行fun函数 kill PID 发出SIGTERM
	//阻塞一下,让这个程序活着
	getchar();
	return 0;
}
##########练习1###########
捕获信号signal
 		SIGINT //打印输出"我不怕ctrl+c!!" //Ctrl + c
		SIGTERM //打印输出"想杀我没门!!" //终端输入: kill PID
		SIGTSTP //打印输出"我是不会停滴!!" //ctrl + z
		ps aux | grep ./a.out   kill 23422

#include "my.h"

//捕获信号后,执行的函数类型是 void (int)
void fun(int num)
{
	//捕获到的是哪个信号,num的值就是那个信号的编号
	switch(num)
	{
	case SIGINT:
		printf("我不怕 ctrl + c!!\n");
		break;
	case SIGTSTP:
		printf("我是不会停滴!!\n");
		break;
	case SIGTERM:
		printf("想杀我,没门!!\n");
		break;
	}
}

int main(int argc, const char *argv[])
{
	//调用signal函数可以捕获信号
	signal(SIGINT, fun);//捕获到SIGINT这个信号后,就会立刻执行fun函数 ctrl + c发出SIGINT
	signal(SIGTSTP,fun);//捕获到SIGTSTP这个信号后,就会立刻执行fun函数 ctrl + z 发出SIGTSTP
	signal(SIGTERM,fun);//捕获到SIGTERM这个信号后,就会立刻执行fun函数 kill PID 发出SIGTERM
	//阻塞一下,让这个程序活着
	getchar();	
	return 0;
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	 4. 发送信号(一个进程给另一个进程发送信号)	 
		kill()数
		1.头文件及函数原型
		#include 
		#include 
		int kill(pid_t pid, int sig);
		
		kill(2345, SIGINT);// 给2345这个进程发送一个SIGINT信号
		kill(2345, 2);// 给2345这个进程发送一个SIGINT信号
		(1)参数说明:	
		    pid_t pid //接收信号进程的PID 
			int sig  //发送的信号
		
		(2)返回值:	成功: 0		失败: -1
		(3)实例	    kill(3111,SIGINT);
##########练习###########
    有两个进程,一个进程接收SIGINT信号,收到后将信号编号打印出来,
     另一个进程负责发送SIGINT信号给那个进程,
	要求第二进程(发送SIGINT信号的进程),进程ID号不写固定值,信号值不写固定值
///接收信号程序的代码 recv.c 
gcc -o recv recv.c 

#include "my.h"

捕获信号后,执行的函数类型是 void (int)///
void fun(int num)
{
	//捕获到的是哪个信号,num的值就是那个信号的编号
	switch(num)
	{
	case SIGINT:
		printf("我不怕 ctrl + c!!\n");
		break;
	case SIGTSTP:
		printf("我是不会停滴!!\n");
		break;
	case SIGTERM:
		printf("想杀我,没门!!\n");
		break;
	}
}

int main(int argc, const char *argv[])
{
	//调用signal函数可以捕获信号
	printf("PID is %d\n",getpid());
	signal(SIGINT, fun);//捕获到SIGINT这个信号后,就会立刻执行fun函数 ctrl + c发出SIGINT
	signal(SIGTSTP,fun);//捕获到SIGTSTP这个信号后,就会立刻执行fun函数 ctrl + z 发出SIGTSTP
	signal(SIGTERM,fun);//捕获到SIGTERM这个信号后,就会立刻执行fun函数 kill PID 发出SIGTERM
	//阻塞一下,让这个程序活着
	getchar();	
	return 0;
}
//发送信号程序的代码 send.c 
gcc -o send send.c 
./send 4723 2
./send 4723 20
./send 4723 15

#include "my.h"

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("忘记传递参数了!!./send PID signum \n ");
		exit(-1);
	}
	pid_t pid = atoi(argv[1]);
	int signum = atoi(argv[2]);

	kill(pid, signum);
	//把PID和信号改为命令行传参
//	kill(4723, 2); //等价于kill(4723, SIGINT);
//	kill(4723, 20);//等价于kill(4723, SIGTSTP);
//	kill(4723, 15);//等价于kill(4723, SIGTERM);
	return 0;
}	

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	3.4 SIGALRM信号(14号)
	linux有一个函数alarm
	unsigned int alarm(unsigned int seconds)
	(1) 功能:能定期产生信号//成为闹钟函数,它可以在进程中设置一个定时器,当定时器时间到,内核
						//向进程发送SIGALAM信号
	(2) 参数:  unsigned int seconds 
	alarm(1);  //1s之后自动产生一个SIGALRM信号 
	alarm(2);  //2s之后自动产生一个SIGALRM信号
	//倒计时功能
	alarm(60)// 60s,调用函数后,进行倒计时,当60s过后,会自动发送一个SIGALRM信号
	(3) 返回值: 
		成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则
		返回上一个闹钟时间的剩余时间,否则返回0。	
		失败: -1
//案例代码举例//
#include "my.h"

void fun(int num)
{
	printf("时间到了, num is %d\n",num);
}

int main(int argc, const char *argv[])
{
	//启动一个闹钟函数,5s,后自动发送一个SIGALRM信号,代表时间到了
	alarm(5);//参数5代表5s
	signal(SIGALRM,fun);//捕获到这个信号之后,立刻去执行fun函数
	printf("1111111111111111111\n");//运行程序111111111瞬间打印,说明alarm(5) 和 signal()函数都没有阻塞功能,只执行了一遍
	getchar();
	return 0;
}
##########练习1###########	
某个进程可以接收SIGALRM信号
每隔 3 秒钟之后,打印"time out"
#include "my.h"

void fun(int num)
{
	printf("time out!!  num is %d\n",num);
	//打印完一次time out之后,说明第一次定的闹钟时间结束,重新定一个闹钟,进行倒计时,再一次,进行3s,倒计时
	alarm(3);
}
int main(int argc, const char *argv[])
{
	alarm(3);//启动定时器,倒计时
	signal(SIGALRM,fun);//异步通信机制
	printf("1111111111111111111\n");
	getchar();//程序一直阻塞在此处
	
	return 0;
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	3.5 SIGCHLD信号  17
	
	SIGCHLD信号,此信号在子进程结束时,会发送此信号给父进程,通知父进程子进程结束了
	子进程的结束,是SIGCHLD信号杀死的吗?? 不是
	
	子进程在结束之后,会给父进程发送一个SIGCHLD信号
	僵尸进程:是坏事,如何避免出现僵尸进程,我们可以利用 SIGCHLD信号,实现 
		      避免僵尸进程

#include "my.h"

void sig_fun(int num)
{
	printf("捕获到了信号SIGCHLD,编号是%d\n",num);
	wait(NULL);//既然收到子进程结束信号,立刻回收资源
}
int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid == 0)
	{
		sleep(3);
		printf("子进程跪了!!\n");
		exit(0);//先结束子进程
	}
	else if(pid > 0)
	{
		//如何避免僵尸进程,由于子进程死后,会自动给父进程发送一个SIGCHLD信号
		signal(SIGCHLD,sig_fun);
		//没有阻塞
		while(1)
		{
			printf("开心的打麻将!!!\n");
			sleep(1);
		}
	}
	return 0;
}

练习:创建一个有名管道mypipe            
    有两个程序read.c 和 wirte.c ,运行的时候,需要命令行传递参数;最终实现了文件的拷贝
    ./read a.c //命令行传参
    ./write b.c //命令行传参
    
    read.c //将a.c中的内容读取出来,写入到管道mypipe
    write.c //从管道mypipe中将内容读取出来,写入到b.c中,实现文件拷贝 

/mkfifo.c//
#include "my.h"

int main(int argc, const char *argv[])
{
	//创建一个有名管道叫mypipe
	int ret = mkfifo("./mypipe",0666);
	if(ret == -1)
	{
		perror("mkfifo failed");
		exit(-1);
	}
	printf("mkfifo OK!!\n");
	return 0;
}
///read.c//
#include "my.h"

int main(int argc, const char *argv[])
{
	if(argc != 2)
	{
		perror("忘记传递参数了!! ./read a.c\n");
		exit(-1);
	}
	int ret;
	char buf[100] = { 0 };
	//将a.c内容全部读取出来,写入到有名管道mypipe中
	int fdr = open(argv[1],O_RDONLY);
	int fdw = open("./mypipe",O_WRONLY);
	if(fdr == -1 || fdw == -1)
	{
		perror("open failed");
		exit(-1);
	}
	//循环读取a.c的内容,读取一次写入一次有名管道,实际读取多少个字节就写入多少个字节
	while((ret=read(fdr, buf, sizeof(buf))) > 0)
	{
		write(fdw, buf, ret);//第三个参数是ret,读取多少就写入多少
	}
	//关闭文件 
	close(fdr);
	close(fdw);
	return 0;
}	
//write.c/
#include "my.h"

int main(int argc, const char *argv[])
{
	if(argc != 2)
	{
		perror("忘记传递参数了!! ./write a.c\n");
		exit(-1);
	}
	int ret;
	char buf[100] = { 0 };
	//将mypipe内容全部读取出来,写入到b.c文件中
	int fdr = open("./mypipe",O_RDONLY);
	int fdw = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666);
	if(fdr == -1 || fdw == -1)
	{
		perror("open failed");
		exit(-1);
	}
	//循环读取mypipe的内容,读取一次写入一次b.c,实际读取多少个字节就写入多少个字节
	while((ret=read(fdr, buf, sizeof(buf))) > 0)
	{
		write(fdw, buf, ret);//第三个参数是ret,读取多少就写入多少
	}
	//关闭文件 
	close(fdr);
	close(fdw);
	return 0;
}

4.3信号量

4. 信号量:用于解决进程之间的同步和互斥问题
	线程里面有一个信号量 
	sem_t sem;
	sem_init(&sem, 0, 0); //同步
	sem_init(&sem, 0, 1); //互斥
	
	sem_wait(&sem);// -1 请求信号量 
	sem_post(&sem);// +1 释放信号量
	进程里面的信号量和线程里面的信号量,思想原理一模一样 
	进程里面的信号量操作函数,非常麻烦,封装的不够好,使用麻烦
	大神,提前自己帮我们封装好了 几个函数,供我们使用
	
	//这三个函数,是我们自己写的
	int init_sem(int semid, int num, int val); //理解方式 sem_init()
	int sem_p(int semid, int num); //理解方式 sem_wait();请求信号量 
	int sem_v(int semid, int num); //理解方式 sem_post();释放信号量
		
  4.1创建信号量
	semget()函数
	(1)功能:创建或取得一个信号量
	(2)	头文件及函数原型:
		#include 
		#include 
		#include 
		int semget(key_t key, int nsems, int semflg);
		
	(3) 参数说明:
		1) key:取得同一个信号量集对象
		2) nsems:信号量的个数  一般是1
		3)emflg: 可以是0(取得) 也可以是IPC_CREAT 

	(4)返回值:
		成功:返回信号量集的IPC表示符
		失败:返回 -1
	(5)实例:
	int semid;
	semid = semget(key,1,IPC_CREAT | 0666);	


semctl函数
(1)功能:初始化和删除信号量函数: 
	(1) 初始化信号量的共享资源数
		semctl(semid,0,SETVAL,0);
	(2) 删除一个信号量
		semctl(semid,IPC_RMID,NULL); //删除一个信号量对象
(2)头文件及函数原型:
	#include 
	#include 
	#include 
	int semctl(int semid, int semnum, int cmd, ...);

(3)参数说明:
	1) int semid //semget返回值
	2) int semnum //操作信号在信号集中的编号。从0开始
	3) SETVAL //设置信号量集中的一个单独的信号量的值
	
(4) 信号量应用 
	semctl(semid,0,SETVAL,0);
	//semid  信号量对象ID,semget返回值
	//0      操作信号在集合中的编号 编号为0
	//SETVAL 设置信号量集中的一个单独的信号量的值
	//0      编号为0的信号量,共享资源数为0个 (即0个停车位)



semop函数
(1)功能:对信号量进行操作
	信号量的本质是计数器 操作的本质是计数器的加减  计数器不允许出现负值 如果已经是0 还要减1 
	这个操作会被阻塞 直到不是0 才可以减1。
(2)头文件及函数原型:
	   #include 
       #include 
       #include 
       int semop(int semid, struct sembuf *sops, unsigned nsops);

(3)参数说明:
		1)int semid //semget返回值
		2)
		struct sembuf结构体:
		{
			unsigned short sem_num;  /* semaphore number */要操作的信号量编号 一般是0 表示第一个信号量
			short          sem_op;   /* semaphore operation */计数器的操作 一般+1或者-1 表示计数器+1或者-1
			short          sem_flg;  /* operation flags */SEM_UNDO或者IPC_NOWAIT
		}		
		3) unsigned nsops:操作数组sops中操作的个数,一般为1(一个操作)

sem.h//
#ifndef __SEM_H__
#define __SEM_H__

#include 
#include 
#include 
#include 

union semun {
	int              val;    /* Value for SETVAL */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO
								(Linux-specific) */
};

int init_sem(int semid, int num, int val);
int sem_p(int semid, int num);
int sem_v(int semid, int num);

#endif

sem.c//
#include "sem.h"

//int semid 信号量ID 
//int num 信号量对象得编号 停车场的编号  是从0开始编号
//int val 信号量资源的个数, 停车位的个数
int init_sem(int semid, int num, int val)
{
	union semun myun;
	myun.val = val;
	if(semctl(semid, num, SETVAL, myun) < 0)
	{
		perror("semctl");
		exit(1);
	}
	return 0;
}
//请求信号量,请求成功,信号量资源数-1,请求失败阻塞等待 类似于线程里面 sem_wait()
//int semid 消息队列的ID 
//int num 停车场的编号  
int sem_p(int semid, int num)
{
	struct sembuf mybuf;
	mybuf.sem_num = num;
	mybuf.sem_op = -1; //信号量资源数-1  停车位-1 
	mybuf.sem_flg = SEM_UNDO;
	if(semop(semid, &mybuf, 1) < 0)
	{
		perror("semop");
		exit(1);
	}
	return 0;
}
//释放信号量,信号量资源个数+1, 类似于线程里面的 sem_post()
//int semid 消息队列的ID 
//int num 停车场的编号 
int sem_v(int semid, int num)
{
	struct sembuf mybuf;
	mybuf.sem_num = num;
	mybuf.sem_op = 1; //信号量资源数+1  停车位+1
	mybuf.sem_flg = SEM_UNDO;
	if(semop(semid, &mybuf, 1) < 0)
	{
		perror("semop");
		exit(1);
	}
	return 0;
}



#include "my.h"
#include "sem.h"

int main(int argc, const char *argv[])
{
	// 两个停车场 
	// 0 1
	//1.创建一个信号量,就是为了得到信号量的ID
	int semid = semget(1111,1,IPC_CREAT|0666);
	if(semid == -1)
	{
		perror("semget failed");
		exit(-1);
	}
	printf("sem id is %d\n",semid);
	init_sem(semid, 0, 1);//将编号是0的停车场初始化,停车位为1个,共享资源数为1

	int shmid = shmget(1234, sizeof(int),IPC_CREAT|0666);
	if(shmid == -1)
	{
		perror("shmget failed");
		exit(-1);
	}
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		exit(-1);
	}
	else if(pid > 0)
	{
		int* p = (int*)shmat(shmid, NULL, 0);
		while(1)
		{
			//sem_p 和 sem_v 中的0,代表的是编号为0的停车场
			sem_p(semid, 0);//请求信号量,如果请求成功,资源数-1,失败阻塞等待 等价于线程的sem_wait
			*p = 10;
			sem_v(semid, 0);//释放信号量 等价于线程的sem_post
		}
	}
	else if(pid == 0)
	{
		int* p = (int*)shmat(shmid, NULL, 0);
		while(1)
		{
			sem_p(semid, 0);//请求信号量,如果请求成功,资源数-1,失败阻塞等待 等价于线程的sem_wait
			*p = 200;
			sleep(2);
			printf("*p is %d\n",*p);
			sem_v(semid, 0);//释放信号量  ==等价于线程的sem_post
		}
	}
	return 0;
}
/编译的时候,注意 gcc test.c sem.c 

4.4共享内存


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5. 共享内存(shm   share memory)	//share 分享  memory 内存 
	共享内存: 进程间通信效率最高的方式(可以直接读写内存)
	
	//使用共享内存的过程是怎样的???,特点是什么???
	特点:通信效率最高
	(1)创建共享内存(shmget()) //得到共享内存的ID,身份证号
	(2)将共享内存映射到自己的进程空间(shmat())//得到共享内存的首地址
	(3)对共享内存进行读写 //写入 *p = 100; 读取 printf("*q is %d\n",*q);
	(4)解除共享内存映射(shmdt())//解除映射
	(5)删除共享内存(shmctl()) //删除
	
	//在使用共享内存的时候,有哪些问题,是你需要去注意的???
	多进程在使用共享内存的时候,要注意 互斥和同步的问题
	
	//怎么解决同步和互斥的问题呢???
		用进程通信里面的 信号量 来解决同步和互斥的问题
	
	5.1 什么是共享内存?

	   linux 内核提供了一块缓冲区,然后两个或多个进程可以共享访问, 
	不像管道,要再定义缓冲区进行存储,共享内存对缓冲区直接访问,
	所以,在多进程通信机制中,共享内存效率最高。

	5.2 共享内存同样会带来一个问题?
	
	   共享内存因为是在多进程间访问,容易出现互斥的问题,需要同步

	5.3 共享内存使用步骤:
		(1)创建共享内存(shmget()) //得到共享内存的ID,身份证号
		(2)将共享内存映射到自己的进程空间(shmat())//得到共享内存的首地址
		(3)对共享内存进行读写 //写入 *p = 100; 读取 printf("*q is %d\n",*q);
		(4)解除共享内存映射(shmdt())//解除映射
		(5)删除共享内存(shmctl()) //删除
		
------------------------------------------------------------------------------------
		//a 创建一块共享内存(或者获得一块共享内存)  share memory	
		(1)头文件及函数原型
		
		#include 
		#include 
		#include 

		int shmget(key_t key, int size, int shmflg);
		//调用 申请的共享大小是4个字节,存一个整数
		// #define IPC_PRIVATE 0
		int shmid = shmget(IPC_PRIVATE, sizeof(int), 0666);
		if(shmid == -1)
		{
			perror("shmget failed");
			exit(-1);
		}
		
		(2)功能: 创建一个共享内存
		(3)参数:
			key 创建共享内存时的内存key值,如果值为IPC_PRIVATE(0), 表示此共享内存是私有的,
				只能在亲缘进程之间使用,非0值表示,创建一个指定ID的共享内存
				多个进程可以通过key值访问同一共享内存	
				
			size 创建的共享内存的大小
			shmflg 创建共享内存的访问权限 0666
			
		(4)返回值:
			成功:是共享内存的id, 能唯一识别一块共享内存 
			失败:-1

		(5)实例:
			私有共享内存 //在有亲缘关系进程间使用
			int shmid = shmget(IPC_PRIVATE, 1sizeof(int), 0666);
			int shmid = shmget(0, sizeof(int), 0666);

			公共共享内存 //在非亲缘关系的进程间使用
			int shmid = shmget(55555, sizeof(int), IPC_CREAT | 0666);

------------------------------------------------------------------------------------
		//b 将共享内存映射到进程自己的内存空间
		
		(1)头文件及函数原型
		
			#include 
			#include 
			#include 
			void *shmat(int shmid, const void *shmaddr, int shmflg); //malloc返回值理解类似
			
			//调用
			int* p = (int*)shmat(shmid, NULL, 0);
		(2)功能:将共享内存映射到进程自己的内存空间 //得到共享内存首地址
		(3)参数:
			1)shmid 是shmget的返回值
			2)shmaddr 如果非NULL值,表示将共享内存映射到指定地址,  通常写NULL
			3)shmflg 通常是 0, 表示共享内存可读可写,也可以写成SHM_RDONLY 只读
			
		(4)返回值:映射到进程的有效地址,通过此地址,可以访问共享内存

		(5)实例:char *p = shmat( shmid, NULL, 0);

------------------------------------------------------------------------------------
		//c 对共享内存读写
		写   
		  *p = 100;
		  
		读
		  printf("%d\n", *p);//直接读取内存中的内容
		  a = *p;

------------------------------------------------------------------------------------
		//d 解除共享内存映射
	    (1)头文件及函数原型
		#include 
		#include 
		#include 
		
		int shmdt(const void *shmaddr);
		//调用
		shmdt(p);
		(2)功能:解除共享内存映射
		(3)参数: shmaddr 共享内存映射后的地址
		(4)返回值:	成功 0	失败 -1
		(5)实例:    shmdt(p);   //解除共享内存映射

------------------------------------------------------------------------------------
		5 删除共享内存
		(1)头文件及函数原型
	
		#include 
		#include 
		#include 
		int shmctl(int shmid, int cmd, struct shmid_ds *buf);
		
		//调用 
		shmctl(shmid, IPC_RMID, NULL);
		(2)功能:删除共享内存
		(3)参数:
				shmid:要操作的共享内存标识符
				cmd :   IPC_STAT  (获取对象属性)
						IPC_SET (设置对象属性)
						IPC_RMID (删除对象)
				buf :  指定IPC_STAT/IPC_SET时用以保存/设置属性

		(4)返回值:	成功 0	失败 -1
		(5)实例:   shmctl(shmid, IPC_RMID, NULL);//删除共享内存

------------------------------------------------------------------------------------
		6 如何实现共享内存
		//案例代码举例,共享内存在两个有亲缘关系的进程之间使用
		//父子进程之间的共享内存通信
		//创建一个共享内存,4个字节,子进程写入一个整数,父进程从共享内存中读取数据,打印输出
#include "my.h"

int main(int argc, const char *argv[])
{
	//1.创建一个共享内存,私有的,在亲缘关系进程之间使用,内存大小4byte,存一个整数
	int shmid = shmget(IPC_PRIVATE, sizeof(int),0666);
	if(shmid == -1)
	{
		perror("shmget failed");
		return -1;
	}
	pid_t pid = fork();
	if(pid  == -1)
	{
		perror("fork failed");
		exit(-1);
	}
	else if(pid == 0)//说明子进程
	{
		//子进程想要写入数据,先得到首地址
		//2.将共享内存的首地址映射到自己的进程空间
		int* p = (int*)shmat(shmid, NULL, 0);
		//3.向共享内存中写入数据
		*p = 100;
		//4.解除映射
		shmdt(p);
	}
	else if(pid > 0)//说明父进程
	{
		wait(NULL);//等子进程写入结束之后,父进程在读取共享内存中的内容
		//父进程要想读取数据,也要先得到首地址
		int* q = (int*)shmat(shmid, NULL, 0);
		//直接读取共享内存中的内容
		printf("共享内存中的数据是:%d\n",*q);
		//解除映射 
		shmdt(q);
		//5.删除共享内存
		shmctl(shmid, IPC_RMID, NULL);
	}
	return 0;
}

	
共享内存在两个不相关的进程之间使用
		int a[10];// 栈 
		int* p = malloc(10*sizeof(int));// 堆 
		进程A向共享内存中,写入hello world, 进程B,从共享内存中读取出并打印

///my.h/
#ifndef _MY_H
#define _MY_H
//如何避免头文件的重复包含

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#endif

///write.c 	
#include "my.h"
int main(int argc, const char *argv[])
{
	//1.创建一个共享内存,得到ID,身份证号
	//因为想用共享内存存储字符串,所以用 100*sizeof(char)
	int shmid = shmget(3333, 100*sizeof(char), IPC_CREAT|0666);
	if(shmid == -1)
	{
		perror("shmget failed");
		return -1;
	}
	printf("shmid is %d\n",shmid);
	//2.将共享内存映射到自己的进程空间,得到首地址
	char* p = (char*)shmat(shmid, NULL, 0);

	//3.将"hello world"字符串,写入共享内存
	//p = "hello world!!"; //这样写,让指针变量p指向字符串hello world,并没有将字符串存储到共享内存中
	strcpy(p, "hello world!!");
	//4.解除共享内存映射
	shmdt(p);
	return 0;
}
///read.c///
#include "my.h"

int main(int argc, const char *argv[])
{
	//1.创建一个共享内存,得到ID,身份证号
	//因为想用共享内存存储字符串,所以用 100*sizeof(char)
	//第一个参数key值是几,不重要,但是两个进程必须用同一个key值,
	//因为用同一个key可以得到同一个共享内存的ID
	int shmid = shmget(3333, 100*sizeof(char), IPC_CREAT|0666);
	if(shmid == -1)
	{
		perror("shmget failed");
		return -1;
	}
	printf("shmid is %d\n",shmid);
	//2.将共享内存映射到自己的进程空间,得到首地址
	char* p = (char*)shmat(shmid, NULL, 0);
	//3.直接读取共享内存的内容
	printf("共享内存中的数据是:%s\n",p);
	//4.解除共享内存映射
	shmdt(p);
	//5.删除共享内存内存,要想使用ipcs -m 命令查看的共享内存信息,不要调用删除共享内存
	//shmctl(shmid, IPC_RMID, NULL);
	return 0;
}		
		
	
//如何通过命令查看 共享内存的信息 和 删除共享内存	
	//ipcs -m 查看共享内存信息
	//ipcrm -m 589830 删除 共享内存为 589830 的这块共享内存


	############练习###############
	练习:两个程序,写进程向共享内存中手动写入10个数保存到共享内存中
	读进程将写进程写入的10个数读取,并打印

///write.c//
#include "my.h"

int main(int argc, const char *argv[])
{
	//1.创建一个共享内存,得到ID,身份证号
	int shmid = shmget(4444, 10*sizeof(int), IPC_CREAT|0666);
	if(shmid == -1)
	{
		perror("shmget failed");
		return -1;
	}
	printf("shmid is %d\n",shmid);
	//2.将共享内存映射到自己的进程空间,得到首地址
	int* p = (int*)shmat(shmid, NULL, 0);
	//3.向共享内存写入10个整数
	int i;
	for(i = 0; i < 10; i++)
	{
		printf("请输入第%d个数:\n",i+1);
		scanf("%d",&p[i]);//scanf("%d",p+i);
	}


	//4.解除共享内存映射
	shmdt(p);
	return 0;
}
///read.c///

#include "my.h"

int main(int argc, const char *argv[])
{
	//1.创建一个共享内存,得到ID,身份证号
	//因为想用共享内存存储字符串,所以用 100*sizeof(char)
	//第一个参数key值是几,不重要,但是两个进程必须用同一个key值,
	//因为用同一个key可以得到同一个共享内存的ID
	int shmid = shmget(4444, 10*sizeof(int), IPC_CREAT|0666);
	if(shmid == -1)
	{
		perror("shmget failed");
		return -1;
	}
	printf("shmid is %d\n",shmid);
	//2.将共享内存映射到自己的进程空间,得到首地址
	int* p = (int*)shmat(shmid, NULL, 0);

	//3.直接读取共享内存的内容
	int i;
	for(i = 0; i < 10; i++)
	{
		printf("%d\n",p[i]);//printf("%d\n",*(p+i));
	}
	//4.解除共享内存映射
	shmdt(p);
	//5.删除共享内存内存,要想使用ipcs -m 命令查看的共享内存信息,不要调用删除共享内存
	shmctl(shmid, IPC_RMID, NULL);//如果加上这个函数,运行程序后,读取一次之后,共享内存就删除了 ipcs -m查不到了
	return 0;
}

4.5消息队列

6. 消息队列(FIFO)(message queue)	
	message  消息 	queue 队列
	
	6.1 什么是消息队列?
	可以发送和接收多个消息,这些消息可以在消息队列中进行缓存(没取走,还在消息队列中保存)

	6.2 消息队列有哪些特点?	
	
	1 消息先进先出 FIFO
	2 消息是一个整体(用结构体来表示)
	3 消息有类型,接收方可以指定接收某种类型的消息,
		消息队列在接收消息的时候,可以实现对消息的过滤	
	//这个结构体需要我们自己定义
	struct msgbuf
	{
		long mtype;   //表示消息的类型
		char mtext[100];  //消息的正文
	};

	6.3 如何使用消息队列(和使用共享内存类似, 消息队列是IPC中的一种进程间通信方式)
		
		a.创建消息队列//msgget()  
		b.发送消息 //msgsnd();
		c.接收消息 //msgrcv();
		d.删除消息队列//msgctl();
====================================================================================
	//a 创建消息队列(获得消息队列)  //得到消息队列的ID
	message
	(1)头文件及函数原型
	
	#include 
	#include 
    #include 

    int msgget(key_t key, int msgflg);
	
	int msgid = msgget(5555, IPC_CREAT |0666); //key理解方式和共享内存一样 
	if(msgid == -1)
	{
		perror("msgget failed");
		exit(-1);
	}
	//key值是几不重要,重要的是多个进程使用同一个key值,可以得到同一个消息队列对象的ID
	(2)功能:创建消息队列	
	(3)参数:
	        key_t key //和消息队列相关的key值,通过同一个key来获取同一消息队列的ID
			int msgflg //消息队列的访问权限 0666
	(4)返回值:	成功:消息队列ID	失败:-1
	(5)实例:
			msgget(key_t key, int msgflg);    //message get 
			1) msgget(IPC_PRIVATE, 0666);         //父子进程
			2) msgget(44444, 0666 | IPC_CREAT);   //不相关的进程
====================================================================================
	//b 发送消息
	struct msgbuf
	{
		long mtype;   //表示消息的类型
		char mtext[100];  //消息的正文
	};
	
	(1)头文件及函数原型
		#include 
		#include 
		#include 
		int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
		
		struct msgbuf s;//用来保存消息的类型和消息正文 
		s.mtype = 1;//消息类型是1 
		strcpy(s.mtext,"I miss you!!");
		
		//sizeof(s) - sizeof(long)  结构体大小 - 消息类型的大小 = 消息正文的大小 
		msgsnd(msgid, &s, sizeof(s)-sizeof(long), 0);
		
	(2)功能:  发送消息
	
	(3)参数:
		msqid:消息队列的ID,msgget返回值
		msgp:   要发送的消息的首地址 
		
		struct msgbuf
		{
			long mtype;   //表示消息的类型
			char mtext[100];  //消息的正文
		};
		msgsz  消息正文的字节数  sizeof(s) - sizeof(long)
		msgflg 发送方式选项 0   阻塞(如果消息队列满,会等) 
							 IPC_NOWAIT 不阻塞
	(4)返回值:	成功 0  	失败 -1
	(5)实例:
			struct msgbuf a = {2,"hello"};
			msgsnd(msgid, &a, sizeof(a) - sizeof(long), 0);
====================================================================================
	//c 接收消息
	(1)头文件及函数原型
	#include 
	#include 
	#include 
	ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
		
	//接收调用 
	struct msgbuf s;//用来保存到接收到的消息 
	msgrcv(msgid, &s, sizeof(s)-sizeof(long), 0 ,0); //阻塞接收,当消息队列为空的时候,msgrcv阻塞
	//第四个参数0,代表不按类型接收消息,只接收消息队列中的第一个消息	
	//第五个参数0,代表当消息队列为空的时候,阻塞等待
		
	(2)功能:接收消息
	(3)参数
		1) msgid  msgget的返回值,消息队列的ID		
		2) msgp   要接收的消息存放的位置(接收消息的缓存区) , 发送和接收结构体要对应
		
		struct mtype
		{
			long mtype;   //表示消息的类型
			char mtext[100];  //消息的正文
		};
		
		3) msgsz  消息正文的字节数
		4) msgtyp 0   接收消息队列中第一个消息,不按类型接收
			      >0  接收消息队列中类型值为msgtyp的消息
			   第4个参数>0 ,假设是 10,代表 只接收消息类型为 10 消息 
							假设是 101,代表 只接收消息类型为 101 消息 			
		5) msgflg 	接收方式选项  0  阻塞(如果消息队列空,会等) 
							      IPC_NOWAIT 不阻塞
	(4)返回值:
		   >0  接收的消息的长度    -1  出错
	(5)实例:msgrcv(msgid, &b, sizeof(b) - sizeof(long), 0, 0);
====================================================================================
	//d 删除消息队列 
	(1)头文件及函数原型
	#include 
	#include 
	#include 
	int msgctl(int msqid, int cmd, struct msqid_ds *buf);
	
	(2) 功能:删除消息队列
	(3) 参数:	
		1) msgid  //msgget的返回值,消息队列的ID
		2) int cmd
					IPC_STAT //读取消息队列的属性,并将其保存在buf指向的缓存区中
					IPC_SET //设置消息队列的属性,这个值取自buf参数
					IPC_RMID //从系统中删除消息队列。
		
		3) struct msqid_ds *buf // 消息队列缓存区
	(4) 返回值:成功: 0	失败: -1
	(5) 实例:	
		msgctl(msgid, IPC_RMID, NULL);


---------------代码演示1-------------	
"I miss you!!"
A  ------> B  
//my.h//
#ifndef _MY_H
#define _MY_H
//如何避免头文件的重复包含

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#endif

/send.c发送消息/
#include "my.h"

struct msgbuf
{
	long mtype;//消息类型
	char mtext[100];//消息正文
};

int main(int argc, const char *argv[])
{
	struct msgbuf s;//用来保存即将发送的消息
	//1.创建一个消息队列,得到消息队列ID,身份证号
	int msgid = msgget(4444, IPC_CREAT|0666);
	if(msgid == -1)
	{
		perror("msgget failed");
		exit(-1);
	}
	printf("send: msgid is %d\n",msgid);
	//2.发送消息 
	//先编辑短信,再发送
	s.mtype = 1;//消息类型是1 
	strcpy(s.mtext, "I miss you!!");//消息正文赋值
	msgsnd(msgid, &s, sizeof(s)-sizeof(long), 0);//4个参数0,代表消息队列满的时候,msgsnd阻塞等待
	return 0;
}

///recv.c接收消息///
#include "my.h"

struct msgbuf
{
	long mtype;//消息类型
	char mtext[100];//消息正文
};
int main(int argc, const char *argv[])
{
	struct msgbuf s;//用来保存接收到的消息
	//1.创建一个消息队列,得到ID,注意要用同一个key值来创建
	int msgid = msgget(4444, IPC_CREAT|0666);
	if(msgid == -1)
	{
		perror("msgget failed");
		exit(-1);
	}
	printf("recv: msgid is %d\n",msgid);
	//2.接收消息
	msgrcv(msgid, &s, sizeof(s)-sizeof(long), 0, 0);
	//第4个参数写0,代表接收消息队列中的第一个消息,不按类型接收
	//第5个参数写0,代表如果消息队列为空,接收阻塞等待
	printf("mtype:%ld  mtext:%s\n",s.mtype,s.mtext);
	//3.删除消息队列
	//msgctl(msgid, IPC_RMID, NULL);//加上此函数的调用,ipcs -q 查询不到消息队列的信息了
	return 0;
}
//查看消息队列信息和删除消息队列
	ipcs -q //查看消息队列信息 
	ipcrm -q 34556 //删除消息队列ID是 34556的消息队列

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

命令行传参发送和接收指定消息类型
//接收指定的消息
将发送消息的 消息类型 和 消息正文 改为 命令行传参
send.c发送消息    ./send 1 hello          //./send 2 nihao //发送指定类型的消息
//把 msgrcv(,,,0,)函数的第四个参数改为命令行传参  
recv.c接收消息      ./recv 1     ./recv 2      ./recv 0 //接收指定类型的消息

//send.c
#include "my.h"
struct msgbuf
{
	long mtype;//消息类型
	char mtext[100];//消息正文
};
int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("忘记传递参数了!! ./send 1 hello\n");
		exit(-1);
	}
	struct msgbuf s;//用来保存即将发送的消息
	//1.创建一个消息队列,得到消息队列ID,身份证号
	int msgid = msgget(4444, IPC_CREAT|0666);
	if(msgid == -1)
	{
		perror("msgget failed");
		exit(-1);
	}
	printf("send: msgid is %d\n",msgid);
	//2.发送消息 
	//先编辑短信,再发送
	s.mtype = atoi(argv[1]);//消息类型是1 
	strcpy(s.mtext, argv[2]);//消息正文赋值
	msgsnd(msgid, &s, sizeof(s)-sizeof(long), 0);//4个参数0,代表消息队列满的时候,msgsnd阻塞等待

	return 0;
}

//recv.c
#include "my.h"

struct msgbuf
{
	long mtype;//消息类型
	char mtext[100];//消息正文
};

int main(int argc, const char *argv[])
{
	if(argc != 2)
	{
		printf("忘记传递参数了!! ./recv 0\n");
		exit(-1);
	}
	struct msgbuf s;//用来保存接收到的消息
	//1.创建一个消息队列,得到ID,注意要用同一个key值来创建
	int msgid = msgget(4444, IPC_CREAT|0666);
	if(msgid == -1)
	{
		perror("msgget failed");
		exit(-1);
	}
	printf("recv: msgid is %d\n",msgid);
	//2.接收消息
	msgrcv(msgid, &s, sizeof(s)-sizeof(long), atoi(argv[1]), 0);
	//第4个参数写0,代表接收消息队列中的第一个消息,不按类型接收
	//第5个参数写0,代表如果消息队列为空,接收阻塞等待
	printf("mtype:%ld  mtext:%s\n",s.mtype,s.mtext);
	//3.删除消息队列
	//msgctl(msgid, IPC_RMID, NULL);//加上此函数的调用,ipcs -q 查询不到消息队列的信息了
	return 0;
}



++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

############作业############### //有一定难度,根据实际情况,做不出来,没关系

聊天程序,创建两个进程 A   B
用一个消息对列
进程A和进程B都用多线程,主进程接收键盘数据,发送消息,子线程阻塞接收对方的消息
利用命令行传参 ./chat 1 2 //1 和 2代表消息类型

//gcc -o chat chat.c -lpthread
./chat 1 2  //进程A 发送的消息类型是1,接收的消息类型是2   
./chat 2 1  //进程B 发送的消息类型是2,接收的消息类型是1 

//编程思想: 
//子线程,专门用来接收消息
void* recv_msg(void* p)
{
	while(1)
	{	//接收消息后,立刻打印
		msgrcv();//时刻接收消息 消息队列为空的时候,阻塞等待
		printf("");
	}
}
int main()
{
	
	pthread_create(&id, NULL, recv_msg, NULL);//创建一个线程,专门用来接收消息
	//主进程 输入发送消息, 输入一句话,立刻发送
	while(1)
	{
		scanf();//输入要发送的话 阻塞 
		msgsnd();
	}
}

///方法一,利用全局变量,来解决问题

#include "my.h"

struct msgbuf
{
	long mtype;//消息类型
	char mtext[100];//消息正文
};
int msgid;
int recvtype;//用来保存接收的消息类型
//专门用来接收消息的线程
void* recv_fun(void* p)
{
	struct msgbuf s;//用来保存接收到的消息
	while(1)
	{
		msgrcv(msgid, &s, sizeof(s)-sizeof(long),recvtype,0);
		printf("from%ld: %s\n",s.mtype,s.mtext);
	}
}

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("忘记传递参数了!! ./chat 1 2\n");
		exit(-1);
	}
	pthread_t id;
	struct msgbuf s;//用来保存即将发送的消息
	//1.创建一个消息队列,得到ID,注意要用同一个key值来创建
	msgid = msgget(4444, IPC_CREAT|0666);
	if(msgid == -1)
	{
		perror("msgget failed");
		exit(-1);
	}
	recvtype = atoi(argv[2]);//接收消息的类型 
	//创建一个子线程,专门用来接收对方发送过来的消息
	pthread_create(&id, NULL, recv_fun, NULL);
	//主进程,负责循环输入要发送的话,并msgsnd发送出去
	s.mtype = atoi(argv[1]);
	while(1)
	{
		gets(s.mtext);
		msgsnd(msgid, &s, sizeof(s)-sizeof(long), 0);
	}
	return 0;
}
方法二,线程传递参数实现//
#include "my.h"

struct msgbuf
{
	long mtype;//消息类型
	char mtext[100];//消息正文
};

typedef struct 
{
	int msgid;//保存消息队列的ID
	int recvtype;//用来保存接收的消息类型
}data_t;

//专门用来接收消息的线程
void* recv_fun(void* p)
{
	data_t* q = p;//无类型指针,赋值有类型的指针
	struct msgbuf s;//用来保存接收到的消息
	while(1)
	{
		msgrcv(q->msgid, &s, sizeof(s)-sizeof(long),q->recvtype,0);
		printf("from%ld: %s\n",s.mtype,s.mtext);
	}
}

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("忘记传递参数了!! ./chat 1 2\n");
		exit(-1);
	}
	pthread_t id;
	data_t data;//用来保存消息队列的ID和接收消息的类型
	struct msgbuf s;//用来保存即将发送的消息
	//1.创建一个消息队列,得到ID,注意要用同一个key值来创建
	int msgid = msgget(4444, IPC_CREAT|0666);
	if(msgid == -1)
	{
		perror("msgget failed");
		exit(-1);
	}

	data.msgid = msgid;//消息队列ID 
	data.recvtype = atoi(argv[2]);//接收消息的类型 

	//创建一个子线程,专门用来接收对方发送过来的消息
	pthread_create(&id, NULL, recv_fun, &data);
	//主进程,负责循环输入要发送的话,并msgsnd发送出去
	s.mtype = atoi(argv[1]);//发送消息的类型
	while(1)
	{
		gets(s.mtext);
		msgsnd(msgid, &s, sizeof(s)-sizeof(long), 0);
	}
	return 0;
}

总结

这里对文章进行总结:这个星期学习了进程与线程,了解父子进程、孤儿进程及僵尸进程、fork()函数;进程线程的创建、阻塞等待及wait与exec族函数;进程间通信的6种方式(管道、信号、信号量、共享内存、消息队列及socket套接字);共享库与静态库、静态链接与动态链接等,进行了细致的阐述!!

你可能感兴趣的:(嵌入式Linux,C开发程序设计,linux,ubuntu,c#,visual,studio,code,代码规范)