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

266 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

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

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

1. 进程简介

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

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

img

每个进程之间的资源都是独立的。--------进程可以自己独立的运行、带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)无名管道写端。

img

 #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.从模式

img

 #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播放器需要使用子进程方式启动。

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

img

示例代码:

 #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;
 }