前言:本篇重点关注几个I/O的API,理解fcntl和select的用法。
6.1~6.3 open/close/read/write/lseek/fcntl/select
6.4 串口
6.5 标准I/O开发
fopen/fdopen/freopen/fread/fwrite/getc/fgetc/getchar/putc/fputc/putchar/
gets/fgets/puts/fputs/printf/fprintf/sprtinf/vprintf/vfprintf/vsprintf/scanf/fscanf/sscanf
===================================================================================
6.1.1 系统调用按照功能逻辑大致可分为进程控制、进程间通信、文件系统控制、系统控制、存储管理、网络管理、socket控制、用户管理等几类。
6.1.2 API
有时,一个API需要几个系统调用来共同完成函数的功能。
6.2 Linux中文件及文件描述符概述
Linux中的文件主要分为4种:普通文件、目录文件、链接文件和设备文件。
通常,一个进程启动时,都会打开3个文件:标准输入、标准输出和标准出错处理。
这3个文件分别对应文件描述符为0、1和2(STDIN_FILENO STDOUT_FILENO STDERR_FILENO)
6.3 不带缓存的文件I/O操作
主要用到5个函数:open / read / write / lseek / close
6.3.1 open close
open.c
open函数返回后的文件描述符一定是最小的未用文件描述符。由于一个进程在启动时自动打开了0、1、2三个文件描述符,因此该文件运行结果中返回的文件描述符为3。
6.3.2 read write lseek
ssize_t read(int fd,void *buf,size_t count)
ssize_t write(int fd,void *buf,size_t count)
off_t lseek(int fd, off_t offset, int whence);
write.c
lseek函数是用于在指定的文件描述符将文件指针定位到相应位置。
offset是偏移量,每一读写操作(调用read/write)所需要移动的距离,单位是字节的数量,可正可负(向前移,向后移)
6.3.3 fcntl 在文件已经共享的情况下如何操作。[上锁]文件锁分为建议性锁和强制性锁。
内核和系统都使用强制性锁,采用强制性锁对性能影响很大,每次读写操作都必须检查是否有锁。
在Linux中,实现文件上锁的函数有flock和fcntl。
flock用于对文件施加建议性锁,fcntl不仅可以施加建议性锁,还可以施加强制锁。
同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁???
记录锁分为读取锁和写入锁,其中读取锁称为共享锁,它能够使多个进程都能在 文件的同一部分建立读取锁。
而写入锁称为排斥锁。
int fcntl(int fd,int cmd,struct flock *lock)
cmd F_SETLK
6.3.4 select
fcntl函数解决了文件的共享问题,接下来该处理I/O复用的情况了。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
一般来说,在使用 select 函数之前,首先使用 FD_ZERO 和 FD_SET 来初始化文件描述符集,在使用了 select 函数时,可循环使用 FD_ISSET 测试描述符集,在执行完对相关后文件描述符后,使用 FD_CLR 来清除描述符集。
将句柄给readfds,当句柄可读时,select返回TRUE
将句柄给writefds,当句柄可写时,select返回TRUE
将句柄给exceptfds,当句柄出现异常时,select返回TRUE
然后再用FD_ISSET(句柄, readfds)判断此句柄可读了。
因为上面3个都是集合,select会同时检测多个句柄。(不知道这种理解是否正确)
参考:
https://groups.google.com/forum/#!msg/zhong1985624/aNoXVZc1InE/fIDcXwx0ZbEJ
http://www.examw.com/linux/all/146463/
以下为API:
6.4 嵌入式Linux串口应用开发
UART 是一个并行输入成为串行输出的芯片
因为计算机内部采用并行数据,不能直接把数据发到modem,必须经过UART整理才能进行异步传输。
其过程为:CPU先把准备写入串行设备的数据放到UART寄存器(临时内存块)中,再通过FIFO传送到串行设备。
如果数据不压缩,波特率等于每秒钟传输的数据位数
6.4.1
串口一: /dev/ttyS0
串口二: /dev/ttyS1
6.4.2 串口设置详解
#include<termios.h>
struct termio{
unsigned short c_iflag; /*输入模式标志*/
unsigned short c_oflag;/*输出模式标志*/
unsigned short c_cflag;/*控制模式标志*/
unsigned short c_lflag;/*本地模式标志*/
unsigned char c_line; /*line discipline*/
unsigned char c_cc[NCC]; /*control characters*/
};
1.保存原先串口配置
if(tcgetattr(fd, &oldtio) != 0){
perror("SetupSerial 1");
return -1;
}
2. 激活选项有CLOCAL和CREAD
newtio.c_cflag |= CLOCAL | CREAD;
3.设置波特率
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
4.设置字符大小
options.c_cflag &= ~CSIZE;/*mask the character size bits*/
options.c_cflag |= CS8;
5. 设置奇偶校验位
使能奇校验:
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
使能偶校验:
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
6. 设置停止位
newtio.c_cflag &= ~CSTOPB;
7. 设置最少字符和等待时间
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
8. 处理要写入的引用对象
tcflush(fd, TCIFLUSH);
9.激活配置
tcsetattr(fd, OPTION, &newtio);
if((tcsetattr(fd, TCSANOW, &newtio)) != 0){
perror("com set error");
return -1;
}
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <errno.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> #include <stdlib.h> int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop) { struct termios newtio,oldtio; /*保存测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息*/ if ( tcgetattr( fd,&oldtio) != 0) { perror("SetupSerial 1"); return -1; } bzero( &newtio, sizeof( newtio ) ); /*步骤一,设置字符大小*/ newtio.c_cflag |= CLOCAL | CREAD; newtio.c_cflag &= ~CSIZE; /*设置停止位*/ switch( nBits ) { case 7: newtio.c_cflag |= CS7; break; case 8: newtio.c_cflag |= CS8; break; } /*设置奇偶校验位*/ switch( nEvent ) { case 'O': //奇数 newtio.c_cflag |= PARENB; newtio.c_cflag |= PARODD; newtio.c_iflag |= (INPCK | ISTRIP); break; case 'E': //偶数 newtio.c_iflag |= (INPCK | ISTRIP); newtio.c_cflag |= PARENB; newtio.c_cflag &= ~PARODD; break; case 'N': //无奇偶校验位 newtio.c_cflag &= ~PARENB; break; } /*设置波特率*/ switch( nSpeed ) { case 2400: cfsetispeed(&newtio, B2400); cfsetospeed(&newtio, B2400); break; case 4800: cfsetispeed(&newtio, B4800); cfsetospeed(&newtio, B4800); break; case 9600: cfsetispeed(&newtio, B9600); cfsetospeed(&newtio, B9600); break; case 115200: cfsetispeed(&newtio, B115200); cfsetospeed(&newtio, B115200); break; case 460800: cfsetispeed(&newtio, B460800); cfsetospeed(&newtio, B460800); break; default: cfsetispeed(&newtio, B9600); cfsetospeed(&newtio, B9600); break; } /*设置停止位*/ if( nStop == 1 ) newtio.c_cflag &= ~CSTOPB; else if ( nStop == 2 ) newtio.c_cflag |= CSTOPB; /*设置等待时间和最小接收字符*/ newtio.c_cc[VTIME] = 0; newtio.c_cc[VMIN] = 0; /*处理未接收字符*/ tcflush(fd,TCIFLUSH); /*激活新配置*/ if((tcsetattr(fd,TCSANOW,&newtio))!=0) { perror("com set error"); return -1; } printf("set done!\n"); return 0; }
6.4.3 串口使用详解
1.打开串口
fd = open("/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY);
O_NOCTTY 标志用于通知Linux系统,这个程序不会成为对应这个端口的控制终端。如果没有指定这个标志,那么任何一个输入(诸如键盘中止信号等)都会影响用户的进程。
O_NDELAY标志通知Linux系统,这个程序不关心DCD信号线所处的状态(端口的另一端是否激活或停止)。如果用户指定了这个标志,则进程将会一直处在睡眠状态,直到DCD信号线被激活。
接下来可恢复串口的状态为阻塞状态,用于等待串口数据的读入。
fcntl(fd, F_SETFL, 0);
再接着可以测试打开文件描述符是否引用一个终端设备,以进一步确认串口是否正确打开
isatty(STDIN_FILENO);该函数调用成功则返回0,若失败则返回-1。
这时,一个串口就已经成功打开了,接下来就可以对这个串口进行读、写操作。
下面给出一个完整的打开串口的函数。
/*打开串口函数*/ int open_port(int fd,int comport) { char *dev[]={"/dev/ttyS0","/dev/ttyS1","/dev/ttyS2"}; long vdisable; if (comport==1)//串口 1 { fd = open( "/dev/ttyS0", O_RDWR|O_NOCTTY|O_NDELAY); if (-1 == fd){ perror("Can't Open Serial Port"); return(-1); } } else if(comport==2)//串口 2 { fd = open( "/dev/ttyS1", O_RDWR|O_NOCTTY|O_NDELAY); if (-1 == fd){ perror("Can't Open Serial Port"); return(-1); } } else if (comport==3)//串口 3 { fd = open( "/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY); if (-1 == fd){ perror("Can't Open Serial Port"); return(-1); } } /*恢复串口为阻塞状态*/ if(fcntl(fd, F_SETFL, 0)<0) printf("fcntl failed!\n"); else printf("fcntl=%d\n",fcntl(fd, F_SETFL,0)); /*测试是否为终端设备*/ if(isatty(STDIN_FILENO)==0) printf("standard input is not a terminal device\n"); else printf("isatty success!\n"); printf("fd-open=%d\n",fd); return fd; }
2. 读写串口
write(fd,buff,8);
read(fd,buff,8);
/*写串口程序*/ #include <stdio.h> #include <string.h> #include <sys/types.h> #include <errno.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> #include <stdlib.h> /*读串口程序*/ int main(void) { int fd; int nread,i; char buff[]="Hello\n"; if((fd=open_port(fd,1))<0){//打开串口 perror("open_port error"); return; } if((i=set_opt(fd,115200,8,'N',1))<0){//设置串口 perror("set_opt error"); return; } printf("fd=%d\n",fd); fd=3; nread=read(fd,buff,8);//读串口 printf("nread=%d,%s\n",nread,buff); close(fd); return; }
6.5 标准I/O开发
基本I/O控制不带缓存的,标准I/O操作都是基于流缓冲的。
全缓冲 malloc
行缓冲 fputc
不带缓冲 write stderr
6.5.1 打开和关闭文件
FILE* fopen(const char* path, const char * mode);
FILE* fdopen(int fd, const char * mode);
FILE* freopen(const char *path, const char * mode, FILE* stream);
int fclose(FILE* stream);
6.5.2 文件读写
size_t fread(void * ptr, size_t size, size_t nmemb, FILE* stream);
size_t fwrite(const void * ptr, size_t size, size_t nmemb, FILE* stream);
6.5.3 输入输出
文件打开之后,根据一次读写文件中字符的数目可分为字符输入输出、行输入输出和格式化输入输出
1.字符输入输出
int getc(FILE* stream); int fgetc(FILE* stream); int getchar(void);
int putc(int c, FILE* stream); int fputc(int c, FILE* stream); int putchar(int c);
将标准输入复制到标准输出中去:
fputc(fgetc(stdin), stdout);
2.行输入输出
char* gets(char * s); char fgets(char * s, int size, FILE* stream);
int puts(const char *s); int fputs(const char * s, FILE* stream);
fputs(fgets(s, 80, stdin), stdout);
3.格式化输入输出
int printf(const char *format, ...);
int fprintf(FILE* fp, const char *format, ...);
int sprintf(char * buf, const char *format, ...);
int vprintf(const char * format, va_list arg);
int vfprintf(FILE* fp, const char *format, va_list arg);
int vsprintf(char *buf, const char *format, va_list arg);
int scanf(const char * format, ...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(char *buf, const char * format, ...);
实验:
int main() { int fd; int count = 0; char s[10]={0}; struct flock lock; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; if((fd = open("note",O_RDWR | O_CREAT, 0666)) < 0){ perror("open:"); exit(1); } else printf("open file ok:%d\n",fd); lock.l_type = F_WRLCK; if(fcntl(fd, F_SETLK, &lock) == -1){ perror("write lock"); exit(1); } if((count = write(fd, "hello", sizeof("hello"))) < 0){ perror("write:"); exit(1); } else printf("write:hello\n"); lseek(fd,1,SEEK_SET); lock.l_type = F_UNLCK; if(fcntl(fd, F_SETLK, &lock) == -1){ perror("unlock"); exit(1); } fcntl(fd, F_GETLK, &lock); if(lock.l_type != F_UNLCK){ } else printf("lock.l_type is F_UNLCK\n"); lock.l_type = F_RDLCK; if(fcntl(fd, F_SETLK, &lock) == -1){ perror("read lock:"); exit(1); } if((count = read(fd, s, 3))<0){ perror("read:"); exit(1); } else printf("read from file:%s\n",s); if(close(fd)<0){ perror("close:"); exit(1); } else printf("Close file\n"); exit(0); }