一、从内存角度分析父子进程的资源问题
1、例子1
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
//原本进程的代码
int a = 100;
printf("main\n");
//创建一个子进程
pid_t id = fork();
if(id == -1)//出错
{
printf("fork error\n");
return -1;
}
else if(id >0)//父进程
{
printf("parent:%d a addr:%p value:%d\n",getpid(),&a,a);
}
else if(id == 0)//子进程
{
printf("child:%d a addr:%p value:%d\n",getpid(),&a,a);
exit(0);//让子进程到这里就结束
}
printf("111\n");
//阻塞等待 子进程退出
wait(NULL);
return 0;
}
结果:
结论:
父进程在fork时,会将父进程自己的空间拷贝一份给子进程。
2、例子2
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
//原本进程的代码
int a = 100;
printf("main\n");
//创建一个子进程
pid_t id = fork();
if(id == -1)//出错
{
printf("fork error\n");
return -1;
}
else if(id >0)//父进程
{
a = 250; //父进程修改变量a的值
printf("parent:%d a addr:%p value:%d\n",getpid(),&a,a);
}
else if(id == 0)//子进程
{
sleep(1);
printf("child:%d a addr:%p value:%d\n",getpid(),&a,a);
exit(0);//让子进程到这里就结束
}
printf("111\n");
//阻塞等待 子进程退出
wait(NULL);
return 0;
}
结果:
结论:
父进程 跟子进程 拥有相对独立的内存空间,互相不影响。
二、产生子进程的另一个函数 vfork
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
函数作用
创建一个子进程,子进程 和 父进程 共享 数据段
特点:
1、父进程与子进程共享数据段
2、一定是 子进程先运行,而且是等子进程结束之后,父进程才开始运行
3、当子进程调用exit之后,父进程才会往下执行
4、你在引用的时候,最好尽快结束子进程
例子:
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
//int a = 100;
int main()
{
//原本进程的代码
//int a = 100; //a是局部变量,是栈区
static int a = 100; //a是静态变量,存放在数据段
printf("main\n");
//使用vfork创建一个子进程,父进程与子进程共享数据段
pid_t id = vfork();
if(id == -1)//出错
{
printf("fork error\n");
return -1;
}
else if(id >0)//父进程
{
printf("parent:%d a addr:%p value:%d\n",getpid(),&a,a);
}
else if(id == 0)//子进程
{
a = 250; //子进程修改变量a的值
printf("child:%d a addr:%p value:%d\n",getpid(),&a,a);
sleep(5);//这里就算延时5s,父进程也不会执行,父进程一定是等子进程结束之后才执行
exit(0);//让子进程到这里就结束,接下来父进程就开始执行了
}
printf("111\n");
//阻塞等待 子进程退出
wait(NULL);
return 0;
}
三、exec函数族
#include <unistd.h>
extern char **environ;
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[]);
函数作用
在进程中加载新的程序,覆盖原有的代码,重新运行
参数
path---》创建出子进程之后,你要让子进程执行哪个程序,将那个程序的路径+名字传递进来
比如创建出子进程之后,让他去执行另外一个程序 "./hello"
arg--》执行程序时,需要的参数列表,以自身程序名字为开始,NULL作为结束标志
比如,你要执行 ./hello --》传递参数 123 ---> "he llo" 123 NULL
注意:只要进程 被exec族替换掉之后,在exec函数之后的代码都不会执行了
例子:
//创建一个子进程,子进程 去启动另外一个程序 hello ,并且传递一个参数"hai"给hello
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
//int a = 100;
int main()
{
printf("main\n");
pid_t id = fork();
if(id == -1)//出错
{
printf("fork error\n");
return -1;
}
else if(id >0)//父进程
{
}
else if(id == 0)//子进程
{
//子进程去执行别的进程(应用程序) ./hello
execl("./hello","hello","hai",NULL);
//execl后面的代码都不会执行了
printf("chlid end\n");
exit(0);//让子进程到这里就结束
}
printf("parent end\n");
//阻塞等待 子进程退出
wait(NULL);
return 0;
}
练习1:
写一个程序,这个程序创建一个子进程后,子进程打印haha,然后监控键盘,通过键盘的参数 去启动 另外一个程序,比如键盘输入 hello hai nihao ,就代表启动 hello程序,传递 hai nihao 到程序hello中,并且在程序hello把传递过来的参数打印出来。
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
/*
练习1:写一个程序,这个程序创建一个子进程后,
子进程打印haha,然后监控键盘,通过键盘的参数 去启动 另外一个程序,
比如键盘输入 hello hai nihao ,就代表启动 hello程序,
传递 hai nihao 到程序hello中,并且在程序hello把传递过来的参数打印出来
*/
int main()
{
//printf("main\n");
pid_t id = fork();
if(id == -1)//出错
{
printf("fork error\n");
return -1;
}
else if(id >0)//父进程
{
}
else if(id == 0)//子进程
{
printf("child:%d haha\n",getpid());
char str[10][256];
bzero(str,sizeof(str));//清空数组的内存空间
int i,cnt=0;
printf("child 请输入数据:"); //hello hai nihao
while(1)
{
scanf("%s",str[cnt++]);
char ch = getchar();
if(ch == '\n')
break;
}
//先把你要加载的程序的路径+名字拼接起来
char pathName[1024]={0};
sprintf(pathName,"./%s",str[0]); // ./hello
//子进程去执行别的进程(应用程序) ./hello
switch(cnt-1)
{
case 1:
execl(pathName,str[0],str[1],NULL);
break;
case 2:
execl(pathName,str[0],str[1],str[2],NULL);
break;
}
}
printf("parent end\n");
//阻塞等待 子进程退出
wait(NULL);
return 0;
}
四、进程之间的通信方式
1、为什么要实现进程之间的通信?
比如:
./test --->开启了一个叫做test的进程。
./project--->开启了一个叫做project的进程。
现在我们想要通过 test 去控制 project
2、在linux中,进程之间的通信方式有哪些?
(1)管道通信
管道通信分为 无名管道 和 有名管道,管道是一个特殊的文件,进程通过将数据写入到管道中,另外一个进程从管道中将这个数据读取出来。
(2)信号
通过信号去控制另外一个进程。
(3)消息队列
(4)共享内存
以上通信有一个共同的特点:只能在同一台主机内部的进程使用。
五、无名管道
1、什么是无名管道
无名管道 只能作用于 亲缘之间的进程通信,无名管道相当于一个队列结构,fd[1]为写入端(入队),fd[0]为读出端(出队)。其中信息读出后即删除,再次读取时即为下一个信息。
2、接口函数
#include <unistd.h>
int pipe(int pipefd[2]);
函数作用
创建无名管道文件。无名管道是一个特殊文件,不可由open函数创建。
参数
pipefd[2]两个成员 pipefd[0]和 pipefd[1],他们都是文件描述符。 管道有固定的读端 pipefd0]和固定的写端 pipefd[1]。
pipefd[0] ---读端 只能读取 数据 --读端文件描述符
pipefd[1] ---写端 只能写入 数据 --写端文件描述符
返回值
成功返回 0
失败返回 -1
例子1:创建一个无名管道文件,把他们的文件描述符都打印出来 fd[0] 和 fd[1]
//将无名管道的文件描述符打印出来
#include<stdio.h>
#include <unistd.h>
int main()
{
int fd[2];
//创建一个无名管道文件 ,创建成功之后 pipefd[0] 读端 pipefd[1] 写端
pipe(fd);
printf("fd[0]:%d fd[1]:%d\n",fd[0],fd[1]); //3 4
return 0;
}
3、为什么 无名管道只能用于 亲缘之间的进程通信
无名管道 在一个进程中被创建出来,只能通过继承的方式传递给子进程。也就是说子进程拷贝父进程的代码。
4、无名管道实现父子进程之间的通信
#include<stdio.h>
#include <unistd.h>
int main()
{
int fd[2];
//创建一个无名管道文件 ,创建成功之后 fd[0] 读端 fd[1] 写端
pipe(fd); //注意,不可以使用open打开无名管道,因为它没有名字,创建的时候默认已经打开了
//创建一个子进程 ,实现父进程与子进程的通信
pid_t id = fork();
if(id == -1)//出错
{
printf("fork error\n");
return -1;
}
else if(id >0)//父进程
{
//给子进程发送数据,也就是说将数据写入 fd[1] 管道中
while(1)
{
char sendbuf[1024] ={0};
scanf("%s",sendbuf);
write(fd[1],sendbuf,strlen(sendbuf));
}
}
else if(id == 0)//子进程
{
//sleep(1);
//接收父进程的信息,也就是说从 读端 管道中 读取数据 fd[0]
while(1)
{
char recvbuf[1024]={0};
read(fd[0],recvbuf,sizeof(recvbuf));
printf("child recvbuf:%s\n",recvbuf);
}
exit(0);
}
wait(NULL);
return 0;
}
六、有名管道
1、什么是有名管道文件?
有名管道文件 是 一个有名字的管道文件。因为在linux下,所有的进程都是可以看到这个文件,所以有名管道作用的范围 是整个linux系统中任意的两个进程。
2、如何创建有名管道文件?(mkfifo)
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
函数作用
创建一个有名管道文件
参数
pathname --- 你要创建的这个管道文件 路径 + 名字
mode ---》权限 0777 0666
返回值
成功返回 0
失败返回 -1
3、判断文件是否存在
#include <unistd.h>
int access(const char *path, int amode);
函数作用:判断文件是否存在
参数 :
path --》你要判断哪个文件
amode ---》F_OK 选择 判断文件的功能
返回值
文件存在 0
不存在 -1
例子1:(创建管道文件)
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_FILE "/home/gec/fifo1"
int main()
{
//先判断文件是否存在,如果存在则不用创建了
if(access(FIFO_FILE, F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
{
//所以是不存在的时候才进来创建
//创建一个有名管道文件
if(mkfifo(FIFO_FILE,0777) == -1)
{
perror("mkfifo error");
return -1;
}
}
return 0;
}
结果:
此时在家目录下就可以看到这个有名管道文件。
例子2:使用有名管道文件实现两个进程之间的通信。
//==========================进程1:作为写入端==============================
//06有名管道文件实现两个进程通信write.c
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FIFO_FILE "/home/gec/fifo1"
int main()
{
//先判断文件是否存在,如果存在则不用创建了
if(access(FIFO_FILE, F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
{
//创建一个有名管道文件
if(mkfifo("/home/gec/fifo2",0777) == -1)
{
perror("mkfifo error");
return -1;
}
}
//发送数据 ,往有名管道文件中写入数据
//1、打开有名管道文件
int fd = open(FIFO_FILE,O_RDWR);
if(fd == -1)
{
perror("open fifo error");
return -1;
}
//2、写入数据
while(1)
{
printf("请输入数据:");
char sendbuf[1024]={0};
scanf("%s",sendbuf);
write(fd,sendbuf,strlen(sendbuf));
}
//3、关闭文件
close(fd);
return 0;
}
//=========================================进程2:读取数据=======================
//06有名管道文件实现两个进程通信read.c
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define FIFO_FILE "/home/gec/fifo1"
int main()
{
//先判断文件是否存在,如果存在则不用创建了
if(access(FIFO_FILE, F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
{
//创建一个有名管道文件
if(mkfifo("/home/gec/fifo2",0777) == -1)
{
perror("mkfifo error");
return -1;
}
}
//从管道文件中读取数据,并且打印出来
//1、打开有名管道文件
int fd = open(FIFO_FILE,O_RDWR);
if(fd == -1)
{
perror("open fifo error");
return -1;
}
//2、从管道文件中读取数据
while(1)
{
char recvbuf[1024]={0};
read(fd,recvbuf,sizeof(recvbuf));
printf("recvbuf:%s\n",recvbuf);
}
//3、关闭文件
close(fd);
return 0;
}
总结:
1.管道是创建在内存中,进程结束空间释放,管道不复存在。
2.无名管道和有名管道都是半双工通信,实现双向通信需要建立两个管道。
3.无名管道是linux特殊文件,。
4.无名管道只用于父子进程之间,有名管道可用于任意两个进程之间。
4、练习:实现 两个进程之间可以 互聊
使用有名管道实现两个进程之间 互相聊天
当 有一方 发出 "byebye" ,结束聊天
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#define FIFO_FILE1 "/home/gec/fifo1"
#define FIFO_FILE2 "/home/gec/fifo2"
int main(int argc,char*argv[])
{
//先判断文件是否存在,如果存在则不用创建了
if(access(FIFO_FILE1, F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
{
//创建一个有名管道文件
if(mkfifo(FIFO_FILE1,0777) == -1)
{
perror("mkfifo error");
return -1;
}
}
//先判断文件是否存在,如果存在则不用创建了
if(access(FIFO_FILE2, F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
{
//创建一个有名管道文件
if(mkfifo(FIFO_FILE2,0777) == -1)
{
perror("mkfifo error");
return -1;
}
}
//发送数据 ,往有名管道文件中写入数据
//创建一个子进程
pid_t id =fork();
if(id==-1)
{
perror("fork error");
}
else if(id > 0)//父进程 ---专门 写入数据 FIFO_FILE2
{
//1、打开有名管道文件
int fd1 = open(FIFO_FILE2,O_RDWR);
if(fd1 == -1)
{
perror("open fifo error");
return -1;
}
//2、写入数据
while(1)
{
char endbuf[256]="byby";
printf("请输入数据:");
char sendbuf[1024]={0};
scanf("%s",sendbuf);
write(fd1,sendbuf,strlen(sendbuf));
if(strcmp(endbuf,sendbuf)==0) //退出条件,也就是如果输入了 byby ,那么退出
{
break;
}
}
printf("父进程退出,通知子进程,让子进程也退出\n");
char cmd[1024]={0};
sprintf(cmd,"killall -SIGKILL %s",argv[0]);
system(cmd);
//3、关闭文件
close(fd1);
}
else if(id == 0) //子进程 ---读取数据 FIFO_FILE1
{
//1、打开有名管道文件
int fd2 = open(FIFO_FILE1,O_RDWR);
if(fd2 == -1)
{
perror("open fifo error");
return -1;
}
//2、读取数据
while(1)
{
char endbuf[256]="byby";
char recvbuf[1024]={0};
read(fd2,recvbuf,sizeof(recvbuf));
printf("recvbuf:%s\n",recvbuf);
if(strcmp(endbuf,recvbuf)==0)
break;
}
//3、关闭文件
close(fd2);
exit(0);
}
return 0;
}
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#define FIFO_FILE1 "/home/gec/fifo1"
#define FIFO_FILE2 "/home/gec/fifo2"
int main(int argc,char*argv[])
{
//先判断文件是否存在,如果存在则不用创建了
if(access(FIFO_FILE1, F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
{
//创建一个有名管道文件
if(mkfifo(FIFO_FILE1,0777) == -1)
{
perror("mkfifo error");
return -1;
}
}
//先判断文件是否存在,如果存在则不用创建了
if(access(FIFO_FILE2, F_OK) == -1)//access 判断文件是否存在,如果不存在则返回 -1
{
//创建一个有名管道文件
if(mkfifo(FIFO_FILE2,0777) == -1)
{
perror("mkfifo error");
return -1;
}
}
//从管道文件中读取数据,并且打印出来
pid_t id =fork();
if(id==-1)
{
perror("fork error");
}
else if(id > 0) //父进程 --写入数据 FIFO_FILE1
{
//发送数据 ,往有名管道文件中写入数据
//1、打开有名管道文件
int fd1 = open(FIFO_FILE1,O_RDWR);
if(fd1 == -1)
{
perror("open fifo error");
return -1;
}
//2、写入数据
while(1)
{
char endbuf[256]="byby";
printf("请输入数据:");
char sendbuf[1024]={0};
scanf("%s",sendbuf);
write(fd1,sendbuf,strlen(sendbuf));
if(strcmp(endbuf,sendbuf)==0)
{
wait(NULL);
break;
}
}
printf("父进程退出\n");
//3、关闭文件
close(fd1);
}
else if(id == 0) //子进程 读取 数据 FIFO_FILE2
{
//1、打开有名管道文件
int fd2 = open(FIFO_FILE2,O_RDWR);
if(fd2 == -1)
{
perror("open fifo error");
return -1;
}
//2、读取数据
while(1)
{
char endbuf[256]="byby";
char recvbuf[1024]={0};
read(fd2,recvbuf,sizeof(recvbuf));
printf("recvbuf:%s\n",recvbuf);
if(strcmp(endbuf,recvbuf)==0)
{
break;
}
}
printf("子进程退出,给父进程发送信号,通知父进程也退出\n");
//子进程发送信号杀死父进程
char cmd[1024]={0};
sprintf(cmd,"killall -SIGKILL %s",argv[0]);
system(cmd);
//3、关闭文件
close(fd2);
exit(0);
}
return 0;
}
七、进程通信方式之一 ----信号 (异步通信方式)
1、在linux中,有哪些信号?
如:
19) SIGSTOP
19: 表示信号值
SIGSTOP :信号的名字,其实一个宏
2、分类
1 - 31 为 非实时信号 ,每个信号会有一个默认的执行动作
34-64 为 实时信号
3、常用的信号
2) SIGINT --》ctrl + C
9) SIGKILL --->停止信号
18) SIGCONT --继续信号
19) SIGSTOP --暂停信号 ,比如当前想要暂停播放音乐 实际上就是向当前播放器这个进程 发送这个 暂停信号
4、在linux中,这些信号到底是由谁发出来的?
1)由系统发出来的。
14) SIGALRM ---》当在程序中调用了定时器alarm()时,如果到点了,就会自动发出这个信号。
2)信号还可以由用户来发送。
kill / killall
5、用户如何发送信号给进程?
方法一:
1)查看当前目标进程的PID号。--》ps -ef
gec 5088 4592 0 01:59 pts/2 00:00:00 ./hello
2)通过kill命令 发送 9号信号给该进程 ,杀死这个进程。
kill -9 5088
kill -SIGKILL 5088
方法二:
直接通过killall命令给进程名字发送信号
killall -9 hello
killall -SIGKILL hello
killall -KILL hello
八、在开发板中播放音乐 ---1.mp3文件 ----音频格式的数据文件
使用音频播放器 madplay
1、该音频播放器在开发板中的位置
[root@GEC6818 ~]#which madplay
/usr/bin/madplay
2、查看该播放器的使用手册
[root@GEC6818 ~]#madplay -h
Usage: madplay [OPTIONS] FILE [...] [OPTIONS] -可以省略
Decode and play MPEG audio FILE(s).
3、如何播放音乐呢?
madplay + 音乐的路径名
[root@GEC6818 /IOT/mp3]#madplay left.mp3 -播放 left.mp3音乐
MPEG Audio Decoder 0.15.2 (beta) - Copyright (C) 2000-2004 Robert Leslie et al.
Encoder: Lavf52.24.1
142 frames decoded (0:00:03.7), +2.8 dB peak amplitude, 900 clipped samples
4、如何在代码中播放呢?
char cmd[] = "madplay left.mp3";
system(cmd);
5、如何控制音乐的状态呢?
//暂停音乐实际上就是暂停音乐播放器这个进程
暂停音乐: killall -SIGSTOP madplay
继续播放音乐: killall -SIGCONT madplay
system("killall -SIGSTOP madplay");
九、制作一个简单的音乐播放器
必须要有界面,实现 暂停、播放、停止、继续
#include<stdio.h>
#include <stdlib.h>
enum {
Ready,//准备状态 0
Player, // 1 正在播放
Stop,// 2
Continue,// 3
Kill// 4
};
int MusicStatus = Ready;//表示音乐的状态
int playerMusic()
{
if(MusicStatus == Ready || MusicStatus == Kill)//如果是准备状态 或者是停止状态 才 播放音乐
{
printf("playerMusic\n");
char cmd[] = "madplay test.mp3 &";//&表示的是让音乐在后台播放
system(cmd);
//更新音乐的状态
MusicStatus = Player;
}
}
int stopMusic()//暂停音乐
{
//只能是在正在播放的状态才可以暂停
if(MusicStatus == Player)
{
printf("stopMusic\n");
char cmd[] = "killall -SIGSTOP madplay";
system(cmd);
MusicStatus = Stop;
}
}
int continueMusic()//继续播放
{
//只能是暂停的时候才可以 继续播放
if(MusicStatus == Stop)
{
printf("continueMusic\n");
char cmd[] = "killall -SIGCONT madplay";
system(cmd);
MusicStatus = Continue;
}
}
int killMusic()//停止音乐
{
//什么时候可以停止音乐呢? Player, Stop Continue
if(MusicStatus == Player || MusicStatus == Stop || MusicStatus == Continue)
{
printf("killMusic\n");
char cmd[] = "killall -SIGKILL madplay";
system(cmd);
MusicStatus = Kill;
}
}
int main()
{
while(1)
{
int mode=-1;
printf("[1]播放音乐 [2]暂停音乐 [3]继续播放 [4]停止音乐:");
scanf("%d",&mode);
switch(mode)
{
case Player://播放音乐
playerMusic();
break;
case Stop://暂停音乐
stopMusic();
break;
case Continue://继续播放
continueMusic();
break;
case Kill://停止音乐
killMusic();
break;
}
}
return 0;
}