首页 > 技术文章 > 第六章 文件I/O编程 [notice select() at RIL.pdf] [串口开发]

jimwind 2013-11-27 17:38 原文

前言:本篇重点关注几个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;

}

SetPort
#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。

这时,一个串口就已经成功打开了,接下来就可以对这个串口进行读、写操作。

下面给出一个完整的打开串口的函数。

Open port
/*打开串口函数*/
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);

main of set and open port
/*写串口程序*/
#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);

}
 

 

 

 

 

推荐阅读