今日诗词分享:该段诗词描述的是国产武侠游戏《剑侠情缘三》中的职业——天策(游戏中对唐朝军人的称呼)。
天策
长河落日东都城,铁马戍边将军坟。
尽诛宵小天策义,长枪独守大唐魂。
==========================================================================
主机操作系统:Centos 6.7
交叉编译器环境:arm-linux-gcc-4.5.4 (可通过命令/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc -v查询)
开发板平台: fl2440
Linux内核版本: linux-3.0 .54
==========================================================================
好了,接下来进入正题。之前,我们通过两种方法移植了madplay播放器,现在,开始尝试通过应用程序来实现用按键控制madplay播放器。按键用的是我们自己写的platform的按键驱动,不是内核自带的。
基于我们的程序,fl2440有四个按键,分别设置为:
KEY1:播放/暂停
KEY2:停止
KEY3:上一首
KEY4:下一首
按键控制播放器代码:
/*********************************************************************************
* Copyright: (C) 2017 qicheng
* All rights reserved.
*
* Filename: bbb.c
* Description: This file
*
* Version: 1.0.0(04/27/2017)
* Author: yangni <497049229@qq.com>
* ChangeLog: 1, Release initial version on "04/27/2017 07:49:28 PM"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define KEY1 0x1 //1<<1
#define KEY2 0x2 //1<<2
#define KEY3 0x4 //1<<3
#define KEY4 0x8 //1<<4
/*共享内存申请标记*/
#define PERM S_IRUSR|S_IWUSR
/*双向循环列表:存放歌曲名*/
struct song
{
char songname[64];
struct song *prev;
struct song *next;
};
/*孙子进程id号*/
pid_t gradchild;
/*子进程id号*/
pid_t pid;
/*共享内存描述标记*/
int shmid;
char *p_addr;
/*播放标记*/
int first_key=1;
int play_flag=0;
/*************************************************
Function name: play
Parameter : struct song *
Description : 播放函数
Return : void
Argument : void
**************************************************/
void play(struct song *currentsong)
{
pid_t fd;
char *c_addr;
char *p;
int len;
char my_song[64]="/mp3/song/";
while(currentsong)
{
/*创建子进程,即孙子进程*/
fd = fork();
if(fd == -1)
{
perror("fork");
exit(1);
}
else if(fd == 0)
{
/*把歌曲名加上根路径*/
strcat(my_song,currentsong->songname);
p = my_song;
len = strlen(p);
/*去掉文件名最后的'\n'*/
my_song[len-1]='\0';
printf("THIS SONG IS %s\n",my_song);
execl("/mp3/madplay","madplay",my_song,NULL);
printf("\n\n\n");
}
else
{
/*内存映射*/
c_addr = shmat(shmid,0,0);
/*把孙子进程的id和当前播放歌曲的节点指针传入共享内存*/
memcpy(c_addr,&fd,sizeof(pid_t));
memcpy(c_addr + sizeof(pid_t)+1,¤tsong,4);
/*使用wait阻塞孙子进程,直到孙子进程播放完才能被唤醒;
当被唤醒时,表示播放MP3期间没有按键按下,则继续顺序播放下一首MP3*/
if(fd == wait(NULL))
{
currentsong = currentsong->next;
printf("THE NEXT SONG IS %s\n",currentsong->songname);
}
}
}
}
/*************************************************
Function name: creat_song_list
Parameter : void
Description : 创建歌曲名的双向循环链表
Return : struct song *
Argument : void
**************************************************/
struct song *creat_song_list(void)
{
FILE *fd;
size_t size;
size_t len;
char *line = NULL;
struct song *head;
struct song *p1;
struct song *p2;
system("ls /mp3/song > song_list");
fd = fopen("song_list","r");
p1 = (struct song *)malloc(sizeof(struct song));
printf("==================================song list=====================================\n");
system("ls /mp3/song");
printf("\n");
printf("================================================================================\n");
size = getline(&line,&len,fd);
strncpy(p1->songname,line,strlen(line));
head = p1;
while((size = getline(&line,&len,fd)) != -1)
{
p2 = p1;
p1 = (struct song *)malloc(sizeof(struct song));
strncpy(p1->songname,line,strlen(line));
p2->next = p1;
p1->prev = p2;
}
p1->next = head;
head->prev = p1;
p1 = NULL;
p2 = NULL;
system("rm -rf song_list");
return head;
}
/*************************************************
Function name: startplay
Parameter : pid_t *,struct song *
Description : 开始播放函数
Return : void
Argument : void
**************************************************/
void startplay(pid_t *childpid,struct song *my_song)
{
pid_t pid;
int ret;
/*创建子进程*/
pid = fork();
if(pid > 0)
{
*childpid = pid;
play_flag = 1;
sleep(1);
/*把孙子进程的pid传给父进程*/
memcpy(&gradchild,p_addr,sizeof(pid_t));
}
else if(0 == pid)
{
/*子进程播放MP3函数*/
play(my_song);
}
}
/*************************************************
Function name: my_pause
Parameter : pid_t
Description : 暂停函数
Return : void
Argument : void
**************************************************/
void my_pause(pid_t pid)
{
printf("=======================PAUSE!PRESS K1 TO CONTINUE===================\n");
kill(pid,SIGSTOP); //对孙子进程发送SKGSTOP信号
play_flag = 0;
}
/*************************************************
Function name: my_pause
Parameter : pid_t
Description : 停止播放函数
Return : void
Argument : void
**************************************************/
void my_stop(pid_t g_pid)
{
printf("=======================STOP!PRESS K1 TO START PLAY===================\n");
kill(g_pid,SIGKILL); //对孙子进程发送SKGKILL信号
kill(pid,SIGKILL); //对子进程发送SKGKILL信号
first_key=1;
}
/*************************************************
Function name: conti_play
Parameter : pid_t
Description : 继续函数
Return : void
Argument : void
**************************************************/
void conti_play(pid_t pid)
{
printf("===============================CONTINUE=============================\n");
kill(pid,SIGCONT); //对孙子进程发送SIGCONT信号
play_flag=1;
}
/*************************************************
Function name: next
Parameter : pid_t
Description : 下一首函数
Return : void
Argument : void
**************************************************/
void next(pid_t next_pid)
{
struct song *nextsong;
printf("===============================NEXT MP3=============================\n");
/*从共享内存获得孙子进程播放歌曲的节点指针*/
memcpy(&nextsong,p_addr + sizeof(pid_t)+1,4);
/*指向下首歌曲的节点*/
nextsong = nextsong->next;
/*杀死当前歌曲播放的子进程,孙子进程*/
kill(pid,SIGKILL);
kill(next_pid,SIGKILL);
wait(NULL);
startplay(&pid,nextsong);
}
/*************************************************
Function name: prev
Parameter : pid_t
Description : 上一首函数
Return : void
Argument : void
Autor & date : yuanhui 09.12.08
**************************************************/
void prev(pid_t prev_pid)
{
struct song *prevsong;
/*从共享内存获得孙子进程播放歌曲的节点指针*/
printf("===============================PRIOR MP3=============================\n");
memcpy(&prevsong,p_addr + sizeof(pid_t)+1,4);
/*指向上首歌曲的节点*/
prevsong = prevsong->prev;
/*杀死当前歌曲播放的子进程,孙子进程*/
kill(pid,SIGKILL);
kill(prev_pid,SIGKILL);
wait(NULL);
startplay(&pid,prevsong);
}
/*************************************************
Function name: main
Parameter : void
Description : 主函数
Return : int
Argument : void
**************************************************/
int main(void)
{
int buttons_fd;
int key_value;
struct song *head;
/*打开设备文件*/
buttons_fd = open("/dev/button", 0);
if (buttons_fd < 0) {
perror("open device buttons");
exit(1);
}
/*创建播放列表*/
head = creat_song_list();
printf("===================================OPTION=======================================\n\n\n\n");
printf(" K1:START/PAUSE K2:STOP K3:NEXT K4:PRIOR\n\n\n\n");
printf("================================================================================\n");
/*共享内存:用于存放子进程ID,播放列表位置*/
if((shmid = shmget(IPC_PRIVATE,5,PERM))== -1)
exit(1);
p_addr = shmat(shmid,0,0);
memset(p_addr,'\0',1024);
while(1)
{
fd_set rds;
int ret;
FD_ZERO(&rds);
FD_SET(buttons_fd, &rds);
/*监听获取键值*/
ret = select(buttons_fd + 1, &rds, NULL, NULL, NULL);
if (ret < 0)
{
perror("select");
exit(1);
}
if (ret == 0)
printf("Timeout.\n");
else if (FD_ISSET(buttons_fd, &rds))
{
int ret = read(buttons_fd, &key_value, sizeof key_value);
if (ret != sizeof key_value)
{
if (errno != EAGAIN)
perror("read buttons\n");
continue;
}
else
{
//printf("buttons_value: %d\n", key_value+1);
/*首次播放,必须是按键1*/
if(first_key){
switch(key_value)
{
case KEY1:
startplay(&pid,head);
first_key=0;
break;
case KEY2:
case KEY3:
case KEY4:
printf("=======================PRESS K1 TO START PLAY===================\n");
break;
default:
printf("=======================PRESS K1 TO START PLAY===================\n");
break;
} //end switch
}//end if(first_key)
/*若不是首次播放,则根据不同键值处理*/
else if(!first_key){
switch(key_value)
{
case KEY1:
//printf("play_flag:%d\n",play_flag);
if(play_flag)
my_pause(gradchild);
else
conti_play(gradchild);
break;
case KEY2:
my_stop(gradchild);
break;
case KEY3:
next(gradchild);
break;
case KEY4:
prev(gradchild);
break;
} //end switch
}//end if(!first_key)
}
}
}
close(buttons_fd);
return 0;
}
从main函数入手看,该程序首先打开我们的按键设备。然后创建播放列表,再分配一段共享内存并初始化(用于存放子进程ID,播放列表位置)。然后通过select函数监听按键,通过播放标记first_key判断是否第一次播放,若first_key=1,则调用startplay()函数并把,first_key设为0,然后程序开始播放。
用交叉编译器编译完成后,把它下载到开发板上就可以运行了。注意,先得在开发板上下载好你的歌曲,需要提前把名字改为英文,然后创建文件夹,把歌曲放在一个文件夹下。比如我在根目录下创建了一个/mp3目录,/mp3目录下建了一个song目录,并把歌曲下载到song目录下,所以我的歌曲目录就为/mp3/song/,然后把madplay工具放在/mp3目录下,总之,打开的目录一定要对。
主要函数分析:
一、创建双向循环链表:
struct song *creat_song_list(void)
{
FILE *fd;
size_t size;
size_t len;
char *line = NULL;
struct song *head;
struct song *p1;
struct song *p2;
system("ls /mp3/song > song_list");
fd = fopen("song_list","r");
p1 = (struct song *)malloc(sizeof(struct song));
printf("==================================song list=====================================\n");
system("ls /mp3/song");
printf("\n");
printf("================================================================================\n");
size = getline(&line,&len,fd);
strncpy(p1->songname,line,strlen(line));
head = p1;
while((size = getline(&line,&len,fd)) != -1)
{
p2 = p1;
p1 = (struct song *)malloc(sizeof(struct song));
strncpy(p1->songname,line,strlen(line));
p2->next = p1;
p1->prev = p2;
}
p1->next = head;
head->prev = p1;
p1 = NULL;
p2 = NULL;
system("rm -rf song_list");
return head;
}
(1)system("ls /mp3/song > song_list");
这里相当于execl()函数,用于执行系统命令,将/mp3/song下的内容重定向的song_list中。如果没有指定重定向到哪,就默认打印到屏幕上。
(2)size = getline(&line,&len,fd);
其中fd是FILE数据结构,用于表示一个文件,和文件描述符类似。fd=fopen("song_list","w");这就是说读取song_list文件的数据,将其一行的首地址给&line,line保存读到的文本,len为要读的文件的大小,fd代表文件指针。fd=fopen("song_list","w");其实就是以写的方式打开文件,并返回文件指针给fd,但是这个Line是保存的是一行内容,需要调节指针不断读取文件才能读整个文件的内容。
(3)创建双向循环列表:
因为歌曲需要实现上一首和下一首,所以链表能够前后访问。双向链表拥有头节点和尾节点。
struct song //创建一个双向链表
{
char songname[64];
struct song *prev;
struct song *next;
};
struct song *head; //分别定义song的结构体
struct song *p1;
struct song *p2;
p2 = p1;
p1 = (struct song *)malloc(sizeof(struct song));
strncpy(p1->songname,line,strlen(line));
p2->next = p1;
p1->prev = p2;
头结点head相当于数组中的首地址,可以通过传参head来访问该链表。
二、开始播放函数:
void startplay(pid_t *childpid,struct song *my_song)
{
pid_t pid;
int ret;
/*创建子进程*/
pid = fork();
if(pid > 0)
{
*childpid = pid;
play_flag = 1;
sleep(1);
/*把孙子进程的pid传给父进程*/
memcpy(&gradchild,p_addr,sizeof(pid_t));
}
else if(0 == pid)
{
/*子进程播放MP3函数*/
play(my_song);
}
}
(1)函数调用在main中:
startplay(&pid,head);
这里head是双向链表的返回值,也是它的头节点,相当于首地址。
(2)fork()函数:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程的进程ID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值;
知道fork()函数使用后,我们就可以开始分析这段代码了。首先,创建了一个子进程(克隆一个自己),因为返回值有两次,所以父进程和子进程各做各的事,因为父进程睡眠了1秒。所以子进程就优先完成了调用play函数的工作。接下来我们就来看看play函数。
二、play函数:
void play(struct song *currentsong)
{
pid_t fd;
char *c_addr;
char *p;
int len;
char my_song[64]="/mp3/song/"; //指定歌曲存放路径,最后的“/”不能少
while(currentsong)
{
/*创建子进程,即孙子进程*/
fd = fork();
if(fd == -1)
{
perror("fork");
exit(1);
}
else if(fd == 0)
{
/*把歌曲名加上根路径*/
strcat(my_song,currentsong->songname);
p = my_song;
len = strlen(p);
/*去掉文件名最后的'\n'*/
my_song[len-1]='\0';
printf("THIS SONG IS %s\n",my_song);
execl("/mp3/madplay","madplay",my_song,NULL);
printf("\n\n\n");
}
else
{
/*内存映射*/
c_addr = shmat(shmid,0,0);
/*把孙子进程的id和当前播放歌曲的节点指针传入共享内存*/
memcpy(c_addr,&fd,sizeof(pid_t));
memcpy(c_addr + sizeof(pid_t)+1,¤tsong,4);
/*使用wait阻塞孙子进程,直到孙子进程播放完才能被唤醒;
当被唤醒时,表示播放MP3期间没有按键按下,则继续顺序播放下一首MP3*/
if(fd == wait(NULL))
{
currentsong = currentsong->next;
printf("THE NEXT SONG IS %s\n",currentsong->songname);
}
}
}
}
因为执行该函数之前先执行startplay()函数,
startplay函数已经创建了一个子进程,本函数由子进程执行过程中调用,所以本函数创建的子进程就
相当于孙进程了。
然后孙进程要做的事就是播放当前的歌曲。而子进程则映射了一段内存,把孙进程的id和当前的歌曲节点指针放入该内存,然后用wait()函数阻塞孙进程,直到孙进程播放结束后才能唤醒,当前歌曲指针指向下一首歌。
(1)内存映射 void *shmat(int shmid, const void *shmaddr, int shmflg);
shmat()是用来允许本进程访问一块共享内存的函数。
第一个参数:shmget返回的标识符,
第二个参数:如果 shmaddr 是NULL,系统将自动选择一个合适的地址! 如果shmaddr不是NULL 并且没有指定SHM_RND 则此段连接到addr所指定的地址上 如果shmaddr非0 并且指定了SHM_RND 则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上.SHM_RND命令的意思是取整,SHMLAB的意思是低边界地址的倍数,它总是2的乘方。该算式是将地址向下取最近一个 SHMLAB的倍数。除非只计划在一种硬件上运行应用程序(这在当今是不大可能的),否则不用指定共享段所连接到的地址。所以一般应指定shmaddr为0,以便由内核选择地址。
第三个参数:如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写的方式连接此段shmat返回值是该段所连接的实际地址 如果出错返回-1
(2) wait(等待子进程中断或结束)
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件,这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。
父进程同步等待子进程退出时则调用wait函数,此时父进程可能会有如下三种情形:
① 阻塞(如果其所有子进程都还在运行)。
② 带回子进程的终止状态立即返回(如果已有一个子进程终止,正等待父进程取其终止状态)。
③ 出错立即返回(如果它没有任何子进程)。
(3)execl()函数:
execl("/mp3/madplay","madplay",my_song,NULL);
第一个参数为命令的路径,第二个参数为命名的名字,后面跟的是参数(可以为多个),NULL表示结尾。
三、暂停函数:
void my_pause(pid_t pid)
{
printf("=======================PAUSE!PRESS K1 TO CONTINUE===================\n");
kill(pid,SIGSTOP); //对孙子进程发送SKGSTOP信号
play_flag = 0;
}
该函数调用了kill函数,发送信号将子进程暂停,并把播放标志设置为0 。my_stop(),conti_play()的用法与之类似。
(1) my_pause(gradchild); //调用该函数
(2)SIGKILL和SIGSTOP的区别
SIGKILL提供给管理员杀死进程的权利,SIGSTOP提供给管理员暂停进程的权利, 所以这两个信号不能被忽略和重定义。
(3)int kill(pid_t pid, int sig);
函数说明:kill()可以用来送参数sig 指定的信号给参数pid 指定的进程。参数pid 有几种情况:
- pid>0 将信号传给进程识别码为pid 的进程.
- pid=0 将信号传给和目前进程相同进程组的所有进程
- pid=-1 将信号广播传送给系统内所有的进程
- pid<0 将信号传给进程组识别码为pid 绝对值的所有进程参数 sig 代表的信号编号可参考附录D
错误代码:
1、EINVAL 参数sig 不合法
2、ESRCH 参数pid 所指定的进程或进程组不存在
3、EPERM 权限不够无法传送信号给指定进程
四、切换到下一曲 void next()函数:
void next(pid_t next_pid)
{
struct song *nextsong;
printf("===============================NEXT MP3=============================\n");
/*从共享内存获得孙子进程播放歌曲的节点指针*/
memcpy(&nextsong,p_addr + sizeof(pid_t)+1,4);
/*指向下首歌曲的节点*/
nextsong = nextsong->next;
/*杀死当前歌曲播放的子进程,孙子进程*/
kill(pid,SIGKILL);
kill(next_pid,SIGKILL);
wait(NULL);
startplay(&pid,nextsong);
}
这里重点是将从共享内存获得孙子进程播放歌曲的节点指针,将指针指向下首歌曲的节点,然后停止当前进程,等待子进程退出,再进行下一轮播放。切换到上一首歌曲的函数和该函数类似,只需要将指针指向前驱即可。
五、memcpy()函数
void *memcpy(void*dest, const void *src, size_t n);
功能:从源src所指的内存地址的起始位置开始,拷贝n个字节的数据到目标dest所指的内存地址的起始位置中。
1)src和dest所指内存区域不能重叠,函数返回指向dest的指针。如果src和dest以任何形式出现了重叠,它的结果是未定义的。
2)与strcpy相比,memcpy遇到’\0’不结束,而且一定会复制完n个字节。只要保证src开始有n字节的有效数据,dest开始有n字节内存空间就行。
3)如果目标数组本身已有数据,执行memcpy之后,将覆盖原有数据(最多覆盖n个)。如果要追加数据,则每次执行memcpy()后,要将目标地址增加到要追加数据的地址。
4)source和destin都不一定是数组,任意的可读写的空间均可。
关于 shmget()和shmat()函数的用法可参考原博客:
本文参考博客:http://blog.sina.com.cn/s/blog_95268f5001016gnf.html