环境:mini2440开发板
一、led-player程序在mini2440上运行的流程:
① 开机进入系统后,将会自动运行一个LED服务程序(/etc/rc.d/init.d/leds),leds是一个脚本文件,它调用了并运行/usr/bin/led-player。以下是leds脚本的内容:
#!/bin/sh
base=led-player //定义一个变量方便引用led-player程序
# See how we were called. case "$1" in //开关机时传递的参数,开机为start,关机为stop start) /usr/bin/$base & //开机运行该脚本时自动运行led-player于后台 ;; stop) //关机运行该脚本时终止led-player进程 pid=`/bin/pidof $base` //获取led-player进程号(反引号在shell中表示执行命令) if [ -n "$pid" ]; then //若字符长度非0,则执行kill -9 pid(led-player进程号) kill -9 $pid fi ;; esac exit 0
② led-player运行时会在/tmp目录下创建一个led-control管道文件,如果没有向管道写入规定参数,则执行默认累加器模式。向该管道文件发送不同的参数就可以改变led的闪烁模式:
#echo 0 0.2 >/tmp/led-control
运行该命令后,4个用户led将会以每个间隔0.2秒的时间运行跑马灯;
#echo 1 0.2>/tmp/led-control
运行该命令后,4 个用户led 将会以间隔 0.2秒的时间运行累加器;
#/etc/rc.d/init.d/ledsstop
运行该命令后,4 个用户led 将会停止闪动;
#/etc/rc.d/init.d/ledsstart
运行该命令后,4 个用户led 将会重新开始闪动。
二、led-player.c源码文件解读分析:
(1)重温基础知识:
① static修饰词用来声明变量,如果没有初始化变量值,则默认为0;
② open函数的原型为int open(const chat *pathname,intoflag,…/*多个参数*/),
oflag参数常用取值:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读、写打开
注:很多实现将O_RDONLY定义为0,O_WRONLY定义为1,O_RDWR定义2,以与早期的系统兼容。
O_NONBLOCK:如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。
③ close函数:函数原型为int close(int filedes); filedes参数为文件的描述符,当一个进程终止时,它所有的打开文件都由内核自动关,很多程序一般不显示地使用close关闭打开的文件。
④ perror函数:函数原型为void perror(const char *s);perror( ) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数s所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串。
⑤ unlink函数:函数原型为int unlink(const char *pathname);此函数删除一个现存的目录项,并将由pathname所引用的文件的链接计数减1。如果该文件还有其他链接,则仍可以通过其他链接存取该文件的数据。如果出错,则不对该文件作任何操作。
⑥ mkfifo函数:函数原型为int mkfifo(const char *pathname,mode_tmode);FIFO称为命名管道。管道只能由相关进程使用,它们的共同祖先进程创建了管道。但是通过FIFO,不相关的进程也能交换数据。mkfifo函数用于创建FIFO文件,其中mode参数的规格说明与open函数中的mode相同。
⑦ select函数:函数原型为int select(int nfds,fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);被定义在sys/select.h文件里,使用此函数需要要包含头文件sys/time.h。nfds为需要检查的号码最高的文件描述符加1;readfds为由select()监视的读文件描述符集合;writefds为由select()监视的写文件描述符集合;exceptfds为由select()监视的异常处理文件描述符集合;timeout为超时等待时间。为了维护fd_set类型的参数,会使用下面四个宏:FD_SET(),FD_CLR(), FD_ZERO() 和 FD_ISSET()。这个函数的返回值:
1) 返回-1表示出错。例如在所指定的描述符都没有准备好时捕捉到一个信号;
2) 返回0表示没有描述符准备好。若指定的描述符都没有准备好,而且指定的时间已经超过,则发生这种情况。
3) 返回一个正值说明已经准备好的描述符数。在这种情况下,三个描述符集中仍旧打开的位是对应于已准备好的描述符位。
⑧ fd_set类型:其实是select()机制中的一种数据结构。详见华清远见的《嵌入式Linux应用程序开发详解》。其中select()函数常用的文件描述符处理宏函数有以下几个:
FD_ZERO(fd_set*set):清除一个文件描述符集;
FD_SET(intfd,fd_set *set):将一个文件描述符加入文件描述符集中;
FD_CLR(intfd,fd_set *set):将一个文件描述符从文件描述符集中清除;
FD_ISSET(intfd,fd_set *set):测试该集中的一个给定位是否有变化。
一般来说,在使用select函数之前,首先使用FD_ZERO和FD_SET来初始化文件描述
符集,在使用了select函数时,可循环使用FD_ISSET测试描述符集,在执行完对相关文件描述符测试后,可以使用FD_CLR来清除相应的描述符集。
注:为方便理解select工作机制最好查阅《Unix环境高级编程》的第12章高级I/O。
⑨ struct timeval结构体:结构体原型为
struct timeval
{
long tv_sec; /* Seconds. */
long tv_usec; /*Microseconds. */
};
在select机制中应用时有以下三种情况:
1) timeout=NULL:永远等待。如果捕捉到一个信号则中断此无限期等待。当所有指定的描述符中的一个已准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR。
2) timeout->tv_sec==0&&timeout->tv_usec==0:完全不等待。测试所有指定的描述符并立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。
3) timeout->tv_sec!=||timeout->tv_usec!=0:等待指定的秒数和微秒数。当指定的描述符之一已准
备好,或当指定的时间值已经超过时立即返回。如果在超时时还没有一个描述符准备好,则返回值是0,(如果系统不提供微秒分辨率,则
timeout->tv_usec==0值取整到最近的支持值。)与第一种情况一样,这种等待可被捕捉到的信号中断。
⑩
ioctl函数:函数原型为int ioctl(int fd, unsigned long cmd, …)
;ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设
备的控制命令,后面的省略号是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
⑪ memset函数:函数原型为void *memset(void *s, int ch, size_t n); 将s中前n个字节(typedef unsigned int size_t)用 ch 替换并返回 s 。
⑫ read函数:函数原型为ssize_t read(int filedes,void *buff, size_t nbytes);如read成功,则返回读到的字节数,如已到达文件的尾端,则返回0.
⑬ sscanf函数:函数原型为int sscanf(const char *buf,const char *format, …);从buf里按照format格式读取相应类型的数据到指定地方。
⑭ fprintf函数:函数原型为int fprintf(FILE *fp, const char *format, …);函数将格式化的字符串输出到指定文件fp中。函数成功则返回格式化输出的字节数。(不包括字符串的结尾’ ’),出错返回一个负值,错误原因在error中。
⑮ stderr:标准错误输出,默认输出到终端窗口,文件描述符为2。
(2)led-player源代码及注释
#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 <sys/select.h> #include <sys/time.h> #include <string.h> static int led_fd; //静态变量,没有初始化则默认为0 static int type = 1; static void push_leds(void) { static unsigned step; unsigned led_bitmap; int i; switch(type) { case 0: //当type变量值为0时,实现循环流水灯效果,即1-2-3-4-3-2-1如此循环 if (step >= 6) { step = 0; } if (step < 3) { led_bitmap = 1 << step; } else { led_bitmap = 1 << (6 - step); } break; case 1: /*默认type变量值为1,实现累加器功能,即0000-0001-0010-0011-0100-0101...依次累加(1表示灯亮)*/ if (step > 255) { step = 0; } led_bitmap = step; //此时led_bitmap为8位二进制数 break; default: led_bitmap = 0; } step++; //每次调用执行本函数则step自加1 for (i = 0; i < 4; i++) { ioctl(led_fd, led_bitmap & 1, i); //控制leds设备,分四次写入相应的值,估计i是写入驱动设备的位置参数 led_bitmap >>= 1; } } int main(void) { int led_control_pipe; int null_writer_fd; // for read endpoint not blocking when control process exit double period = 0.5; led_fd = open("/dev/leds0", 0); //以只读方式打开设备leds0 if (led_fd < 0) { led_fd = open("/dev/leds", 0); //mini2440开发板上的是这个设备 } if (led_fd < 0) { perror("open device leds"); exit(1); } unlink("/tmp/led-control"); //删除指定文件 mkfifo("/tmp/led-control", 0666); //创建指定命名管道文件,权限为0666(首位0表示这个数为八进制数) led_control_pipe = open("/tmp/led-control", O_RDONLY | O_NONBLOCK); if (led_control_pipe < 0) { perror("open control pipe for read"); exit(1); } null_writer_fd = open("/tmp/led-control", O_WRONLY | O_NONBLOCK); if (null_writer_fd < 0) { perror("open control pipe for write"); exit(1); } for (;;) { fd_set rds; struct timeval step; int ret; FD_ZERO(&rds); //将rds描述符集清零 FD_SET(led_control_pipe, &rds); //将rds描述符集中描述符led_control_pipe对应的位置1 step.tv_sec = period; step.tv_usec = (period - step.tv_sec) * 1000000L; ret = select(led_control_pipe + 1, &rds, NULL, NULL, &step); //只关心readfds描述符集,其他两个为NULL if (ret < 0) { perror("select"); exit(1); } if (ret == 0) { //指定等待时间已超过而指定的描述符还没准备好 push_leds(); } else if (FD_ISSET(led_control_pipe, &rds)) { static char buffer[200]; for (;;) { char c; int len = strlen(buffer); if (len >= sizeof buffer - 1) { memset(buffer, 0, sizeof buffer); //对buffer数组初始化 break; } if (read(led_control_pipe, &c, 1) != 1) { //从命名管道文件led-control中读取一个字节 break; } if (c == ' ') { //读出字符为回车符,则忽略后面的语句,重新循环 continue; } if (c == ' ') { //读出字符为换行符 int tmp_type; double tmp_period; if (sscanf(buffer,"%d%lf", &tmp_type, &tmp_period) == 2) { type = tmp_type; period = tmp_period; } fprintf(stderr, "type is %d, period is %lf ", type, period); //将参数type、period输出到终端显示 memset(buffer, 0, sizeof buffer); //成功取完一次参数,将buffer数组清零 break; } buffer[len] = c; //将读到的字符写入buffer数组(前面的程序已经实现舍弃回车符和换行符) } } } close(led_fd); //关掉leds设备 return 0; }
参考资料:
①《Unix环境高级编程》
②《嵌入式Linux应用程序开发详解》(华清远见-电子版)
③《C Primer Plus中文版》
④mini2440用户手册
⑤源码来自mini2440开发板自带光盘盘