介绍 Linux 下进程编程知识点,学习 fork 函数、管道、消息队列、共享内存、信号等同通信方式。并且介绍了广告机小项目的实现方法。

任务 1: 学习 Linux 下进程编程

1. 进程简介

进程是操作系统调度的最小单元,线程是进程内部的执行单元,一个进程默认有一个主线程。

进程在操作系统里使用 PID 号作为标识符号----查看当前终端运行的进程: ps 。

每个进程之间的资源都是独立的。--------进程可以自己独立的运行、带 main----------

主要的知识点:

【1】进程间通信: 管道(无名管道、命名管道)、消息队列、共享内存、内存映射(mmap)、信号。

【2】execl 函数族: 用于启动一个新的进程,新的进程开始执行之后,会覆盖原来进程空间。

【3】dup2 函数: 复制文件表。------实现文件描述符重定向。 dup2(fds[1],1);

【4】编写广告机播放器程序

【5】编写 shell 脚本,实现文件同步

1.1 进程创建

#include <unistd.h> pid_t fork(void);

功能: 在当前进程里再创建一个子进程。

函数返回值: ==0 表示子进程,>0 表示父进程,<0 表示出现错误

新创建的子进程特性: 在 fork 成功那一刻,会将父进程所有的资源全部拷贝一份,重新运行。

僵尸进程: 子进程先退出,父进程没有清理子进程的空间。如何清理子进程空间? wait();

孤儿进程: 父进程比子进程先退出。避免,就是父进程要保证最后退出。

1.2 等待子进程退出,并且清理子进程空间

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
函数功能: 随机等待一个子进程退出,并清理子进程资源。
返回值: 返回退出的子进程PID号。
函数的形参: int *status可以保存进程退出的状态。 exit(-1); //结束当前进程。
pid_t waitpid(pid_t pid, int *status, int options);
函数: 可以指定特定的PID号。-1表示所有子进程。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int pid;/*创建子进程*/
pid=fork();
if(pid==0) //子进程
{
printf("子进程正常运行!....\n");
sleep(1);/*结束当前进程*/
exit(0);
}
else if(pid>0) //父进程
{
int state=0; //保存子进程退出状态值
/*阻塞-等待子进程退出*/
wait(&state);
printf("父进程提示: 子进程已经安全退出! 子进程退出的状态=%d\n",state);
}
else
{
printf("进程创建错误!");
}
return 0;
}

复制代码

1.3 终止当前进程

#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
#include <stdlib.h>
void exit(int status);

复制代码

1.4 管道通信

管道: FIFO 文件,特性: 先入先出。

1.4.1 无名管道: 有亲缘关系之间的进程才可以使用无名管道进程通信。

无名管道这个 FIFO 文件没有实体。

如果创建无名管道?

#include <unistd.h>
int pipe(int pipefd[2]);
函数形参: 传入一个数组的首地址。
管道创建成功之后: [0]表示(FIFO)无名管道读端。 [1]表示(FIFO)无名管道写端。

复制代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char **argv)
{
int pid;
int pipefd[2];
/*创建无名管道*/
pipe(pipefd);/*创建子进程*/
pid=fork();
if(pid==0) //子进程
{
printf("子进程正常运行!....\n");
sleep(1);char *p="1234567";
write(pipefd[1],p,strlen(p)+1); //向管道的写端写入数据/*结束当前进程*/
exit(0);
}
else if(pid>0) //父进程
{
int state=0; //保存子进程退出状态值char buff[100];
read(pipefd[0],buff,100); //从管道的读端读取数据
printf("父进程收到的数据=%s\n",buff);/*阻塞-等待子进程退出*/
wait(&state);
printf("父进程提示: 子进程已经安全退出! 子进程退出的状态=%d\n",state);
}
else
{
printf("进程创建错误!");
}
return 0;
}

复制代码

1.4.2 命名管道通信

命名管道可以在任何进程间通,因为命名管道是一个实体文件,在磁盘可用找到该 FIFO 文件。

如何在磁盘上创建管道文件:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

复制代码

管道文件不能在共享目录下创建。(挂载的目录)

1.5 dup2 函数学习

#include <unistd.h>
int dup2(int oldfd, int newfd);
示例: dup2(fds[1],1); //接下来对文件描述符1的操作都是相当于对管道fds[1]操作。
文件描述符在内核里对应的是一个文件结构体。

复制代码

示例 1:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char **argv)
{
int pid;
int pipefd[2];
/*创建无名管道*/
pipe(pipefd);/*创建子进程*/
pid=fork();
if(pid==0) //子进程
{
printf("子进程正常运行!....\n");dup2(pipefd[1],1);
//pipefd[1]管道写端,1表示当前进程的标准输出
sleep(1);
printf("---1234567---\n");/*结束当前进程*/
exit(0);
}
else if(pid>0) //父进程
{
int state=0; //保存子进程退出状态值char buff[100];
read(pipefd[0],buff,100); //从管道的读端读取数据
printf("父进程收到的数据=%s\n",buff);/*阻塞-等待子进程退出*/
wait(&state);
printf("父进程提示: 子进程已经安全退出! 子进程退出的状态=%d\n",state);
}
else
{
printf("进程创建错误!");
}
return 0;
}

复制代码

示例 2: 日志功能制作。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char **argv)
{
/*1.创建存放日志的文件*/
int fd=open("/log.txt",O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);/*2. 重定向(将标准输出重定向到fd)*/
dup2(fd,1);/*3. 向日志文件写入数据*/
printf("12345\n");
printf("abcd\n");
printf("日志文件测试!\n");/*4. 关闭日志文件*/
close(fd);
return 0;
}

复制代码

1.6 execl 函数族

#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[]);

复制代码

execl 功能介绍: 启动新的子进程,当子进程启动成功之后会覆盖原来的进程空间。

Execl 函数族介绍:

【1】带 p 表示可执行文件可以从环境变量里获取。

【2】不带 p 表示,可执行文件需要填绝对路径。

【3】带 e 表示最后的参数,可以给新进程设置新的环境变量。

说明: 参数列表最后面都要加一个 NULL。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char **argv)
{
/*以ls命令为例子,讲解*/
//execl(<可执行程序的路径>,<执行的形参列表>,NULL);//execl("/bin/ls","ls","-l",NULL);//execlp("ls","ls","-l",NULL);//char *envp[]={"PATH1=12345",NULL};
//execle("/bin/ls","ls","-l",NULL,envp);
//获取环境变量的值: getenv("PATH1");//char *argvs[]={"ls","-l",NULL};
//execv("/bin/ls",argvs);char *argvs[]={"ls","-l",NULL};
execvp("ls",argvs);printf("执行失败!\n");
return 0;
}
//ls -l

复制代码

1.7 mplayer 播放器

Mplayer 运行有两个模式: 1. 主模式 2.从模式

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
/*
获取标准输入的数据、写给FIFO文件
*/
void *pthread_func(void *argv)
{
int fd=open("/mplayer_fifo",2);
if(fd<0)
{
printf("FIFO文件打开失败!\n");
pthread_exit(NULL); //结束当前线程
}
char buff[100];
int len;
while(1)
{
printf("请输入命令:");
fflush(stdin); //刷新缓冲区
fgets(buff,100,stdin); //从键盘上获取数据 get_percent_pos get_file_name
len=strlen(buff); // get_file_name [0~12] [13]='\n'
write(fd,buff,len); // get_file_name '\n'
memset(buff,0,100);
}
}
int main(int argc,char **argv)
{
int pid;/*1. 创建无名管道*/
int fds[2];
pipe(fds);/*2. 创建子进程*/
pid=fork();/*子进程代码: mplayer播放器*/
if(pid==0)
{
/*将子进程的标准输出重定向到管道写端*/
dup2(fds[1],1);/*启动子进程*/
execlp("mplayer","mplayer","-zoom","-x","800","-y","480","-slave","-quiet","-input","file=/mplayer_fifo","/work/video_file/Video_2018-12-11.wmv",NULL);
}
else /*父进程*/
{
char buff[100];
int cnt=0;/*创建新的线程: 从键盘上获取输入的数据,写给播放器的FIFO文件*/
pthread_t threadID;
pthread_create(&threadID,NULL,pthread_func,NULL);
pthread_detach(threadID); //设置分离属性while(1)
{
/*从管道的读端读取数据: 读取就是mplayer播放器输出的数据*/
cnt=read(fds[0],buff,100);
buff[cnt]='\0';
printf("播放器输出的值=%s\n",buff);
}
}
return 0;
}

复制代码

任务 2: 广告机项目

广告机项目要求:

广告机应用场景: 公交站台、地铁车厢、银行前台大厅、高速公路、公园….

【1】有些广告机只有视频播放,没有声音。

【2】广告机都支持网络视频文件更新---->文件更新使用现成的服务器: FTP 服务器、NFS 服务器。

(1) 如何判断服务器上那些文件需要下载到本地? 通过 shell 脚本代码或者使用 C 语言。

(2) 更新的时间一般是固定的: 20:00 23:00 …… 通过时间函数判断时间是否到达。

(3) 在视频文件更新的时候,视频需要停止播放,可以在屏幕上显示提示(正在更新…..)。

【3】广告机需要支持自动播放,播放一个自动切换下一个、循环播放。

调用读目录、循环遍历目录、得到视频文件、mplayer 播放器需要使用子进程方式启动。

广告机: 音量调整、选择视频播放…….都不是广告机的功能---是视频播放器的功能。

示例代码:

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/ioctl.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <pthread.h>#include <sys/types.h>#include <dirent.h>#include <signal.h>#include <time.h>
/*-----------全局变量区------------*/int log_fd;  /*保存日志文件的文件描述符*/#define LOG_FILE "/log.txt"   /*日志文件存放的路径*/DIR *video_dir_p=NULL; /*存放视频的目录: 打开的目录指针*/int video_state=0;
#define VIDEO_FILE_PATH "/work/video_file/" //存放视频文件的目录#define UPDATE_HOUR 16 //更新的时间: 小时#define UPDATE_MIN 38  //更新的时间: 分钟#define UPDATE_SEC 00  //更新的时间: 秒
/*-------函数声明---------*/void DeleteListALLNode(void);/*定义链表使用的结构体*/struct VideoList{  char *file_path;  struct VideoList *next;};
/*链表头*/struct VideoList *ListHead=NULL; /*函数: 信号处理函数*/void exit_sighandler(int sig){  /*1. 判断是什么信号*/  if(sig==2)  {    printf("用户终止了进程!\n");  }  else if(sig==11)  {    printf("进程访问了非法内存空间!\n");  }    /*2. 杀死父进程对应所有子进程*/  char cmd_buff[100];  //杀死父进程创建所有子进程,父进程本身不受影响  sprintf(cmd_buff,"pkill -9 -P %d",getpid());  system(cmd_buff);  sleep(2);    /*3. 关闭打开所有的文件或者目录*/  close(log_fd);   closedir(video_dir_p);  DeleteListALLNode();    /*4. 退出父进程*/  exit(1);}
/*函数功能: 获取本地时间,判断是否是否到达预设的时间*/void *Time_pthread_func(void *argv){  time_t time1;  time_t time2;  struct tm *system_time;  char cmd_buff[100];  while(1)  {    time1=time(NULL); //获取当前系统的时间秒单位    if(time1!=time2)  //保证if每1秒进去一次    {      time2=time1;      system_time=localtime(&time1); //将秒单位时间,转成标准时间结构      printf("%d-%d-%d\n",system_time->tm_hour,system_time->tm_min,system_time->tm_sec);      /*更新的时间*/      if(system_time->tm_hour==UPDATE_HOUR       && system_time->tm_min==UPDATE_MIN      && system_time->tm_sec==UPDATE_SEC)      {        video_state=1; //表示进程需要终止                //"pkill -9 -P <父进程的PID>"         //杀死父进程创建所有子进程,父进程本身不受影响        sprintf(cmd_buff,"pkill -9 -P %d",getpid());                /*执行命令*/        system(cmd_buff);        printf("正在结束子进程!\n");        pthread_exit(NULL);      }    }  }}/*函数功能: 扫描目录下的所有文件,加载到链表里*/void ScanDirFile(){  struct dirent *dir_file;  struct VideoList *head_p=ListHead; //保存链表头的地址  struct VideoList *new_p=NULL;    while(dir_file=readdir(video_dir_p))  {    //过滤掉.和..    if(strcmp(dir_file->d_name,".")==0 || strcmp(dir_file->d_name,"..")==0)    {      continue;    }        //创建新节点    new_p=(struct VideoList*)malloc(sizeof(struct VideoList));    if(new_p==NULL)    {      printf("创建新节点空间申请错误!\n");      return;    }        //申请存放文件名称的空间    new_p->file_path=malloc(strlen(VIDEO_FILE_PATH)+strlen(dir_file->d_name)+1);    if(new_p->file_path==NULL)    {      printf("申请存放文件名称的空间错误!\n");      return;    }        //拼接路径    strcpy(new_p->file_path,VIDEO_FILE_PATH);    strcat(new_p->file_path,dir_file->d_name);        printf("播放的列表:%s\n",new_p->file_path);        //添加新的节点    while(head_p->next!=NULL)    {      head_p=head_p->next;    }        head_p->next=new_p;    new_p->next=NULL;  }}
/*
函数功能: 删除链表节点
*/
void DeleteListALLNode(void)
{struct VideoList *head_p=ListHead; //保存链表头的地址struct VideoList *tmp_p;struct VideoList *delete_p;if(head_p!=NULL && head_p->next==NULL){free(head_p); //释放链表头}else if(head_p->next!=NULL){tmp_p=head_p->next;free(head_p); //释放链表头while(tmp_p->next!=NULL){delete_p=tmp_p;tmp_p=tmp_p->next;free(delete_p->file_path);free(delete_p);}free(tmp_p->file_path);free(tmp_p);}
}
int main(int argc,char **argv)
{int pid;int state=0;struct VideoList *next_p=ListHead;/*1. 注册将要捕获的信号*/signal(SIGINT,exit_sighandler);   /*进程终止信号:Ctrl+C*/signal(SIGSEGV,exit_sighandler);  /*进程访问了非法内存*//*2. 创建日志文件: 保存mplayer播放器的输出*/log_fd=open(LOG_FILE,O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);/*3. 打开目录*/video_dir_p=opendir(VIDEO_FILE_PATH);if(video_dir_p==NULL){printf("%s 存放视频的目录打开失败!\n",VIDEO_FILE_PATH);exit(1);}/*4. 创建新的线程:判断更新时间*/pthread_t threadID;pthread_create(&threadID,NULL,Time_pthread_func,NULL);pthread_detach(threadID); //设置分离属性/*5. 遍历目录,更新链表*///创建链表头ListHead=(struct VideoList*)malloc(sizeof(struct VideoList));if(ListHead==NULL){printf("链表头创建失败!\n");exit(1);}ListHead->next=NULL; //下个节点为NULL//扫描目录,并将目录下的文件添加到链表ScanDirFile();next_p=ListHead;    //链表头next_p=next_p->next; //取出数据节点LOOP: //该标签表示继续播放下一个视频时,重复创建子进程
  printf("正在播放视频名称:%s\n",next_p->file_path);  /*创建子进程*/  pid=fork();
/*子进程代码: mplayer播放器*/ if(pid==0) { /*将子进程的标准输出重定向到日志文件*/ dup2(log_fd,1);  /*启动子进程*/ execlp("mplayer","mplayer","-zoom","-x","800","-y","480","-slave","-quiet","-input","file=/mplayer_fifo",next_p->file_path,NULL);
  }/*父进程代码: 控制播放*/else{/*等待子进程退出*/wait(&state);//判断是否需要结束当前进程if(video_state==1){/*执行外部脚本: 启动视频文件更新*/system("./update_video.sh");/*退出父进程*/DeleteListALLNode();close(log_fd); closedir(video_dir_p);exit(0);}/*遍历链表的下一个节点,取出文件名称,传递给子进程*/if(next_p->next==NULL) //表示视频播放完毕{printf("视频播放完毕---->链表归位!\n");next_p=ListHead;    //链表头next_p=next_p->next; //取出数据节点}next_p=next_p->next;   //取出数据节点/*再次启动子进程,播放下一个视频*/goto LOOP; }return 0;
}

Linux 驱动开发 _ 视频广告机开发、Linux 进程编程介绍相关推荐

  1. esp32系列(11):ESP32 IDF平台 mpu6050 DMP 驱动移植及测试上位机开发

    目录 1 DMP 官方库介绍 1.1 DMP与MPL(Motion Processing Libraries)功能 1.2 运行MPL的硬件要求 1.3 Motion Driver 6.12 的架构 ...

  2. a33 linux 硬解码_全志A33 lichee 开发板 Linux中断编程原理说明

    开发平台 * 芯灵思SinlinxA33开发板 嵌入式linux 开发板交流 641395230 本节实验目标实现按键触发中断终端显示按键松开或按下 实验平台 芯灵思Sinlinx A33 开发板 s ...

  3. linux服务器怎么连接无线网卡,在linux上怎么安装无线网卡驱动?_网站服务器运行维护,linux,无线网卡...

    怎么查看linux连接wifi的密码?_网站服务器运行维护 查看linux连接wifi密码的方法:首先打开终端输入代码[cd /etc/NetworkManager/system-connection ...

  4. android应用程序开发_抚顺小程序开发定制找谁,吉林小程序定制

    湖南庚午网络科技有限公司为您详细解读kpuZae抚顺小程序开发定制找谁的相关知识与详情,微信小法式,简称小法式,缩写XCX,英文名Mini Program,是一种不需要#安拆即可使用的应用,它实现了应 ...

  5. 如果成为一名高级安卓开发_什么是高级开发人员,我如何成为一名开发人员?

    如果成为一名高级安卓开发 Becoming a Senior Developer is something many of us strive for as we continue our code ...

  6. gwt前台开发_为GWT设置开发环境

    gwt前台开发 介绍 这是旨在用Java开发跨平台移动应用程序的系列文章的一部分 . 在此博客文章中,我们将了解GWT是什么,并为GWT设置开发环境. GWT是一个开源开发工具包,用于开发基于浏览器的 ...

  7. 简单Android app开发_如何简单快速开发外卖app?

    如何开发一个外卖app?app开发需要多少钱?随着美团.饿了么的外卖app的发展,对餐饮.生鲜果蔬.超市便利店行业来说,app成为必不可缺少的一部分.与其向第三方交纳一定不开发一个自己的外卖平台.也有 ...

  8. java云开发_快速入门云开发

    什么是云开发 这里引用官方的一段描述: 云开发(Tencent CloudBase,TCB)是云端一体化的后端云服务 ,采用 serverless 架构,免去了移动应用构建中繁琐的服务器搭建和运维.同 ...

  9. Linux驱动与一般应用的区别【Linux驱动之路一】

    Linux驱动和一般应用的区别,大致可以归类为以下几点: 一. Linux驱动 属于内核级,驱动程序的崩溃会导致整个系统的崩溃,例如在驱动程序中出现了非法指针的应用,就会导致系统的崩溃.因此要十分注意 ...

最新文章

  1. Nature Methods:快速准确的微生物来源追溯工具FEAST
  2. @CreatedDate@CreatedBy@LastModifiedBy@LastModifiedDate
  3. 移动互联网改变商业环境:商品的颠覆
  4. GPU 加速下的图像视觉
  5. Phpcms之核心目录phpcms
  6. CentOS中安装mysql
  7. git 可视化工具_版本控制可视化神器Gource:简单易上手,效果恰似烟花秀
  8. ping网关丢包_网络/摄像机丢包的原因分析
  9. java 方法 示例_Java语言环境getDisplayCountry()方法与示例
  10. 鱼池正式宣布支持EIP-1559
  11. 从中关村的小小柜台,但目前市值千亿元的公司
  12. Lambda表达式实现有限状态机
  13. 淘淘商城第17讲——引用Dubbo服务
  14. Excel 固定表头
  15. 2019字节跳动实习面试
  16. [No000030]程序员节发点别的:中国教育整个把人脑子搞坏了-易中天
  17. 2021-11-12
  18. 【已解决】极速迅雷win10闪退解决方案
  19. html5手机 一键开发,Html5变革下的H5和手机app开发工具
  20. JKS 密钥库使用专用格式。建议使用 “xxx“ 迁移到行业标准格式 PKCS12

热门文章

  1. 威尔克姆教程:他他米的偏移系数及分割线
  2. 神经网络和贝叶斯网络关系
  3. 无向图生成树计数 -- Kirchhoff 矩阵法模板
  4. CISAW认证,与CISP认证有啥区别?
  5. CISAW安全集成考试有了新变化
  6. 华为java校招面试流程_华为面试流程是什么?
  7. java 进制转换,二进制 十进制 十六进制 正数 负数的进制等等!
  8. 中国植物绝缘油变压器行业研究与投资前景报告(2022版)
  9. javaSE进阶练习—— Collection的三种遍历方式练习
  10. android手机做音乐软件,安卓手机必备的五个黑科技APP,每个都强大到没有朋友!要低调使用...