嵌入式Linux系统编程学习之九基于文件描述符的文件操作
文章目录
- 前言
- 一、文件描述符
- 二、打开、创建和关闭文件
- 三、读写文件
- 四、改变文件大小
- 五、文件定位
- 六、原子操作
- 七、进一步理解文件描述符
- 八、文件描述符的复制
- 九、文件的锁定
- 十、获取文件信息
- 十一、access函数
- 十二、标准输入/输出文件描述符
- 十三、时间和日期相关函数
- 十四、处理的模型(补充)
- 十五、串口编程
- 十六、日志
- 总结
前言
一、文件描述符
一个程序(进程)可以在运行的过程中同时打开多个文件,每个程序运行起来后,系统就有一个记录表专门记录这个程序打开的各个文件。每打开一个文件,记录表就会用一个新的结构体变量来保存这个文件的相关信息。如果打开多个文件,记录表中就会有多个这样的结构体变量分别保存多个文件的相关信息,它们构成了一个结构体数组,而数组的每一个元素的下标就是文件描述符。
文件描述符是一个较小的非负数(0~1023),它代表记录表的每一项,即上面说的数组中的某个元素的下标。通过文件描述符和一组基于文件描述符的文件操作函数就可以实现对文件的打开、关闭、读写、删除等操作。
文件描述符的函数有open(打开)、creat(创建)、close(关闭)、read(读取)、write(写入)、ftruncate(改变文件大小)、lseek(定位)、fsync(同步)、fstat(获取文件状态)、fchmod(权限)、flock(加锁)、fcntl(控制文件属性)、dup(复制)、dup2、select和ioctl。
基于文件描述符的文件操作并非ANSI C(标准C)的函数,而是Linux的系统调用,即Linux下的API函数。
二、打开、创建和关闭文件
open()函数和creat()函数都能打开和创建文件,原型为:
#include <sys/types.h> //头文件
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags); //文件名 打开方式
int open(const char *pathname, int flags, mode_t mode); //文件名 打开方式 权限
int creat(const char *pathname, mode_t mode); //文件名 权限
int close(int fd); //fd表示文件描述符
creat()函数等价于open(pathname, O_CREAT | O_TRUNC | O_WRONLY, mode);
open()和creat()函数出错时返回-1,成功时返回一个整数(即文件描述符);
flags表示打开或创建的方式;mode表示文件的访问权限;
对于open()函数来说,第三个参数mode仅当创建新文件时(即O_CREAT)才生效,打开已有的文件不生效。
flags的可选项:
掩码 | 含义 |
---|---|
O_RDONLY | 以只读的方式打开 |
O_WRONLY | 以只写的方式打开 |
O_RDWR | 以读写的方式打开 |
O_CREAT | 如果文件不存在,则创建文件 |
O_EXCL | 仅与O_CREAT连用,若文件已存在,则强制open失败 |
O_TRUNC | 如果文件存在,则将文件的长度截至0 |
O_APPEND | 以追加的方式打开文件,每次调用write时,文件指针自动先移到文件尾,用于多进程写同一个文件。 |
O_NONBLOCK | 非阻塞方式打开,无论有无数据读取或等待,都会立即返回进程之中 |
O_NODELAY | 非阻塞方式打开 |
O_SYNC | 同步打开文件,只有在数据被真正写入物理设备后才返回 |
mode的可选项有:
掩码 | 掩码值 | 含义 |
---|---|---|
S_IRWLRY | 00700 | 代表该文件所有者具有可读、可写及可执行的权限。 |
S_IRUSR 或 S_IREAD | 00400 | 代表该文件所有者具有可读取的权限 |
S_IWUSR 或 S_IWRITE | 00200 | 代表该文件所有者具有可写入的权限 |
S_ILRYSR 或 S_IEXEC | 00100 | 代表该文件的所有者具有可执行的权限 |
S_IRWXG | 00070 | 代表该文件用户组具有可读、可写及可执行的权限 |
S_IRGRP | 00040 | 代表该文件用户组具有可读的权限 |
S_IWGRP | 00020 | 代表该文件用户组具有可写入的权限 |
S_IXGRP | 00010 | 代表该文件用户组具有可执行的权限 |
S_IRWXO | 00007 | 代表其他用户具有可读、可写及可执行的权限 |
S_IROTH | 00004 | 代表其他用户具有可读的权限 |
S_IWOTH | 00002 | 代表其他用户具有可写入的权限 |
S_IXOTH | 00001 | 代表其他用户具有可执行的权限 |
注意:对于不存在的文件open时必须加上O_CREAT选项,文件使用完毕后,应调用close关闭。
三、读写文件
读写文件的函数原型为:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count); //文件描述符 缓冲区 长度
ssize_t write(int fd, const void *buf, size_t count);
对于read()和write()函数,出错返回-1,读取完成返回0,其他情况返回读写个数。
四、改变文件大小
函数原型为:
#include <unistd.h>
int ftruncate(int fd, off_t length);
函数ftruncate()会将参数fd指定的文件大小改为参数length指定的大小。参数fd为已打开的文件描述符,且必须是以写入模式打开的文件。若原来的文件大小比参数length大,则超过部分会被删去。执行成功返回0,失败返回-1。
五、文件定位
函数原型:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函数lseek()将文件指针设定到相对于whence偏移值为offset的位置;
whence为以下常量中的一个:
- SEEK_SET:从文件头开始计算
- SEEK_CUR:从当前指针开始计算
- SEEK_END:从文件尾开始计算
利用该函数可以实现文件空洞(对一个新建的空文件,可以定位到偏移文件开头1024字节的地方,再写入一个字符,则相当于给该文件分配了1025字节的空间,形成文件空洞),通常用于多进程间通信时共享内存。
六、原子操作
对文件的读写原子操作有pread、pwrite:
ssize_t pread(int fd, void *buf, size_t count, off_t offset);//文件描述符 缓冲区 长度 相对于文件头偏移量
ssize_t pwrite(int fd, const void * buf, size_t count, off_t offset);
对文件的读写操作很重要,不能被打断,可以用原子操作函数。
七、进一步理解文件描述符
如果一个程序打开一个文件,open()函数就会返回一个文件描述符。它的值由系统分配的。实际上,当一个程序打开第一个文件时,分配的文件描述符的值是3,第二个是4,…就这样从此往后排。
八、文件描述符的复制
函数dup和dup2可以实现文件描述符的复制,此时文件描述符是没有打开的最小文件描述符。原型为:
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
文件描述符的复制是指用另外一个文件描述符指向同一个打开的文件,它完全不同于直接给文件描述符变量赋值;例如描述符变量的直接赋值:
char buf[32];
int fd1 = open("./a.txt", O_RDONLY);
int fd2 = fd1; //类似于C语言的指针赋值,当释放掉一个时,另一个已经不能操作了
close(fd1); //导致文件立即关闭
printf("read:%d\n", read(fd2, buf, sizeof(buf)-1)); //读取失败
close(fd2); //无意义
在此情况下,两个文件描述符变量的值相同,指向同一个打开的文件,但内核的文件打开引用计数还是1,所以close(fd1)或者close(fd2)都会导致文件立即关闭。
描述符的复制:
char buf[32];
int fd1 = open("./a.txt", O_RDONLY);
int fd2 = dup(fd1); //内核的文件打开引用计算+1,变成2了
close(fd1); //当前还不会导致文件被关闭,此时通过fd2依然可以访问文件
printf("read:%d\n", read(fd2, buf, sizeof(buf)-1));
close(fd2); //内核的引用计数变为0,文件正式关闭
此时fd2如果修改了文件内容,则文件内容将会改变,及fd1和fd2中有一个修改文件,则对应另一个也变。
dup2(int fdold, int fdnew)也是进行描述符的复制,采取此种复制,新的描述符由用户用参数fdnew显式指定,而不是像dup一样由内核帮你选定(内核选定的是从最小找的);对于dup2,如果fdnew已经指向一个已经打开的文件,内核会首先关闭掉fdnew所指向的原来的文件。此时再针对fdnew文件描述符操作的文件,则采用的是fdold的文件描述符。如果成功,dup2的返回值与fdnew相同,否则为-1。
九、文件的锁定
在多进程对同一个文件进行读写访问时,为了保证数据的完整性,有时需要对文件进行锁定。
可以通过fcntl对文件进行锁定和解锁:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd); //文件描述符,操作的命令
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock * lock);
int ioctl(int d, int request, ...); //用来控制硬件,如光驱、硬盘、摄像头、LCD等
返回值:成功返回0,有错误返回-1。
参数struct flock为结构体,表示文件锁信息,如下所示:
struct flock
{short int l_type; //锁定的状态 用F_RDLCK、F_WRLCK、F_UNLCKshort int l_whence; //决定l_start位置 用SEEK_SET、SEEK_CUR、SEEK_ENDoff_t l_start; //锁定区域的开头位置off_t l_len; //锁定区域的大小 用struct stat结构体中的st_size可以获得pid_t l_pid; //锁定动作的进程 用getpid()函数获得
};
//通常l_start和l_len都设为0,l_whence为SEEK_SET表示整个文件
参数cmd,常用的如下:
加锁解锁
置为F_SETLK:设置文件锁定的状态。此时flcok结构体的l_type值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1。
对于F_RDLCK:共享锁(或读锁),许多不同的进程可以拥有文件同一(或重叠)区域上的共享锁。只要任一进程拥有共享锁,那么就不能再有进程可以获得该区域上的独占锁。为了获得一把共享锁,文件必须为“读”或“读/写”方式打开。
对于F_WRLCK:独占锁(或写锁),只有一个进程可以在文件的任一特定区域拥有独占锁。一旦一个进程拥有了这样的锁,其他任何进程都无法在该区域上获得任何类型的锁。为了获得一把独占锁,文件必须为“写”或“读/写”方式打开。
对于F_UNLCK:解锁,用来清除锁。
置为F_GETLK:获取文件锁定的状态。此时同样需要对第三个参数指向的结构体填充,即设置为相应的锁,fcntl会取得第一个能够阻止事先设置的锁生成的锁。取得的该锁的信息将覆盖传到fcntl()的flock结构的信息。如果没有发现能够阻止本次锁(flock)生成的锁,这个结构体l_type将为F_UNLCK,并不是上锁文件没有被上锁,而是说别人对这个文件设置的锁不会阻碍这个锁的获得,所以对我们来说相当于是解锁的状态。
给文件加锁的步骤:
int main()
{int fd = open("1.dat", O_RDONLY); //1.open打开文件//计算文件长度,因为设置flock结构体时要设置长度l_lenstruct stat st;fstat(fd, &st);int len = st.st_size;struct flock lock; //2.设置flock结构体的参数lock.l_type = F_RDLCK; //设置加锁类型为读锁lock.l_whence = SEEK_SET; //从头往后lock.l_start = 4; //开始位置lock.l_len = len; //大小lock.l_pid = getpid(); //进程fcntl(fd, F_SETLK, &lock); //3.加锁sleep(10);close(fd); //4.关闭文件return 0;
}
文件锁有两种类型:读取锁(共享锁)和写入锁(互斥锁)。对于已经加读取锁的文件,再加写入锁将会失败,但是会允许其他进程继续加读取锁;对于已经加写入锁的文件,再加读取锁和写入锁都将会失败。
注意:文件锁只会对其他试图加锁的进程有效,对直接访问文件的进程无效,也就是说自己对一个文件加了锁,自己是检测不到锁的状态的。自己是可以读写文件,其他程序也可以读写这个加锁的文件。
加上相应的锁只是告诉别人我此时在访问文件,起一个标识的作用;当多个进程所修改的文件区不同时就可以并行执行,有交叉时就可以互斥执行。
另外,在对加了锁的文件进行读写操作时,应采用底层的read和write函数,因为高层的fread和fwrite函数会有缓冲区,这样就不能实现同步更新了。
十、获取文件信息
可以通过fstat和stat函数获取文件信息,调用完毕后,文件信息被填充到结构体struct stat变量中,函数原型为:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char * file_name, struct stat * buf); //文件名 stat结构体指针
int fstat(int fd, struct stat * buf); //文件描述符 stat结构体指针
int lstat(const char * file_name, struct stat * buf);
lstat函数类似于stat,但是当命名的文件是一个符号链接时,lstat得到该符号链接文件的有关信息,而不是该符号链接指向的文件的信息。
结构体stat的定义为:
struct stat
{dev_t st_dev; //如果是设备,返回设备表述符,否则为0ino_t st_ino; //i节点号mode_t st_mode; //文件类型nlink_t st_nlink; //链接数uid_t st_uid; //属主IDgid_t st_gid; //组IDdev_t st_rdev; //设备类型off_t st_size; //文件大小,字节表示blksize_t st_blksize; //块大小blkcnt_t st_blocks; //块数time_t st_atime; //最后访问时间time_t st_mtime; //最后修改时间time_t st_ctime; //创建时间
};
对于结构体的成员st_mode,有一组宏可以进行文件类型的判断,如下表:
宏 | 描述 |
---|---|
S_ISLNK(mode) | 判断是否是符号链接 |
S_ISREG(mode) | 判断是否是普通文件 |
S_ISDIR(mode) | 判断是否是目录 |
S_ISCHR(mode) | 判断是否是字符型设备 |
S_ISBLK(mode) | 判断是否是块设备 |
S_ISFIFO(mode) | 判断是否是命名管道 |
S_ISSOCK(mode) | 判断是否是套接字 |
通常用于判断:if(S_ISDIR(st.st_mode)) { }
十一、access函数
Linux下不同用户对一个文件的可操作权限不同,access函数可以检测当前用户对某文件是否有某权限。按实际用户ID和实际组ID进行权限许可测试的。
#include <unistd.h>
int access(const char * pathname, int mode); //要检测的文件名 要检测的权限
mode可以是以下宏:R_OK读,W_OK写,X_OK执行,F_OK测试文件是否存在;有相应权限返回0,没有返回-1。
十二、标准输入/输出文件描述符
与标准输入/输出流对应,在更底层的实现是用标准输入、标准输出、标准错误文件描述符表示的,它们分别用STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO三个宏表示,值分别是0、1、2三个整型数字。
标准输入文件描述符–>STDIN_FILENO–>0
标准输出文件描述符–>STDOUT_FILENO–>1
标准错误输出文件描述符–>STDERR_FILE–>2
这就是打开一个文件返回的文件描述符从3开始分配的原因。
十三、时间和日期相关函数
日历时间:从一个标准时间点到此时的时间经过的秒数。Linux系统中,这个标准时间点是1970年1月1日 00:00:00。用time_t数据类型来表示从那一刻到现在所经过的秒数。
格林威治时间:即国际标准时间
本地时间:本地时区的时间
#include <time.h>
time_t time(time_t *t);
struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);
time_t:保存日历时间的数据类型
tm:保存时间的结构体
struct tm结构体如下:
struct tm {int tm_sec;int tm_min;int tm_hour;int tm_mday;int tm_mon;int tm_year;int tm_wday;int tm_yday;int tm_isdst;
};
注:由于年是从1900年开始计算的,所以要加上1900,月份要加1。我国是东8区,所以本地时间的小时比国际标准时间多8小时。
十四、处理的模型(补充)
阻塞I/O模型:在这种模型下,若所调用的I/O函数没有完成相关的功能,则会使进程挂起,直到相关数据到达才会返回。如对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。
非阻塞模型:在这种模型下,当请求的I/O操作不能完成时,则不让进程睡眠,而且立即返回。非阻塞I/O使用户可以调用不会阻塞的I/O操作,如open()、write()、read()。如果该操作不能完成,则会立即返回出错或者返回0。
I/O多路转接模型:在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,而是让其中一个函数等待,在这期间,I/O还能进行其他操作,如select函数。
信号驱动I/O模型:在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O,这是由内核通知用户何时可以启动一个I/O操作决定的。
异步I/O模型:在这种模型下,当一个描述符已经准备好,可以启动I/O时,进程会通知内核。
select函数:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptionset, const struct timeval *timeout);
返回:就绪描述字的正数目,0—超时,-1—出错
select函数参数:
maxfd:最大的文件描述符
readset:内核读操作的描述符字集合
writeset:内核写操作的描述符字集合
exceptionset:内核异常操作的描述符字集合
timeout:等待描述符就绪需要多少时间;NULL代表永远等下去,一个固定值代表等待固定时间,0代表根本不等待,检查描述字之后立即返回。
fd_set集合的相关操作:
void FD_ZERO(fd_set *fdset); //将所有fd清零
void FD_SET(int fd, fd_set *fdset); //增加一个fd
void FD_CLR(int fd, fd_set *fdset); //删除一个fd
int FD_ISSET(int fd, fd_set *fdset);//判断一个fd是否有设置
一般来说,在使用select函数之前,首先要使用FD_ZERO和FD_SET来初始化文件描述符集,在使用select函数时,可循环使用FD_ISSET测试描述符集,在执行完对相关文件描述符之后,使用FD_CLR来清除描述符集。
struct timeval结构体如下:
struct timeval
{long tv_sec; //秒long tv_usec; //微秒
}
十五、串口编程
who命令可以查看当前打开了多少个终端,每个终端在/de/pts目录下都有一个对应的设备文件。
系统只开一个终端时:
#who
root tty1 2021-09-15 16:19 (:0)
root pts/0 2021-09-15 08:48 (:0.0)
说明:上面输出的第2行就是第一个终端,对应 /dev/pts/0这个设备。
如果要把内容输入到第1个终端,则可以直接使用Linux的echo命令完成。
如下:
echo "hello" >/dev/pts/0
串口概述:用户常见的数据通信的基本方式分为并行通信和串行通信。
并行通信:利用多条数据传输线将一个资料的各位同时传送。特点是传输速度快,适用于短距离通信,但要求传输速度较高的应用场合。
串行通信:利用一条传输线将资料一位一位地顺序传送。特点是通信线路简单,利用简单的线缆就可以实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。
常见的一些串口参数的配置:
序号 | 信号名称 | 符号 | 流向 | 功能 |
---|---|---|---|---|
2 | 发送数据 | TXD | DTE->DCE | DTE发送串行数据 |
3 | 接收数据 | RXD | DTE<-DCE | DTE接收串行数据 |
4 | 请求发送 | RTS | DTE->DCE | DTE请求DCE将线路切换到发送方式 |
5 | 允许发送 | CTS | DTE<-DCE | DCE告诉DTE线路已接通可以发送数据 |
6 | 数据设备准备好 | DSR | DTE<-DCE | DCE准备好 |
7 | 信号地 | 信号公共地 | ||
8 | 载波检测 | DCD | DTE<-DCE | 表示DCE接收到远程载波 |
20 | 数据终端准备好 | DTR | DTE->DCE | DTE准备好 |
22 | 振铃指示 | RI | DTE<-DCE | 表示DCE与线路接通,出现振铃 |
Linux中,串口文件位于/dev下,串口1为/dev/ttyS0,串口2为/dev/ttyS1。
tty看当前自己的设备文件名称,stty -a看终端所有属性。
用如下命令配置终端:
#stty erase x(将退格键设置为x)#stty eof ^E(将文件结束设置为Ctrl+E)#stty - echo(取消回显)#stty echo(加上回显)#minicom -s(选port设置,按A:/dev/ttyS0,按E:先按E再按Q)
串口的操作步骤如下:
在串口编程之前,需要执行下面的操作,首先你的计算机上要安装Serial Port(串口);例如,在VM下的settings…下的"Hardware"下可以执行Add添加,然后在列表中选中"Serial Port",在右边选择"Use output file",然后浏览保存到windows下的一个文件中,这样就可以直接简单的进行串口编程了。
int fd = open("/dev/ttyS0", O_RDWR | O_SYNC);char buf[8] = {"hello"};write(fd, buf, 8);close(fd);
也可以直接利用echo “asjdfl” >/dev/ttyS0往里面写内容。
(1)打开串口时通过使用标准的文件打开函数操作:
int fd = ("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);//以读写方式打开串口if(-1 == fd){perror("提示错误!");exit(-1);}
O_NOCTTY标志通知Linux系统,这个程序不会成为对应这个端口的控制终端;如果没有指定这个标志,那么任何一个输入都将会影响用户的进程。
O_NDELAY标志通知Linux系统,这个程序不关心DCD信号线所处的状态;如果用户指定了这个标志,则进程将会一直处于睡眠状态,直到DCD信号线被激活。
接下来可以恢复串口的状态为阻塞状态,用于等待串口数据的读入;可以用fcntl函数实现:
fcntl(fd, F_SETFL, 0);
再接下来可以测试打开文件描述符是否引用一个终端设备,以进一步确认串口是否正确打开:
isatty(STDIN_FILENO);
(2)设置串口的波特率、校验位和停止位,设置信息在 struct termios 结构体中:
struct termios {unsigned short c_iflag; //输入模式unsigned short c_oflag; //输出模式unsigned short c_cflag; //控制模式unsigned short c_lflag; //本地模式unsigned short c_cc[NCC]; //控制字符};
在这个结构体中,最重要的是c_cflag,通过对它的赋值,用户可以设置波特率、字符大小、数据位、停止位、奇偶校验位和硬件流控等;其中设置波特率为相应的波特率前加上’B’,还有c_iflag和c_cc也是比较常用的标志;
f_cflag支持的常量名称:
符号 | 定义 | 符号 | 定义 |
---|---|---|---|
CBAUD | 波特率的位掩码 | CSIZE | 数据位的位掩码 |
B0 | 0波特率 | CS5 | 5个数据位(发送和接收时使用5比特) |
B1800 | 1800波特率 | CS6 | 6个数据位 |
B2400 | 2400波特率 | CS7 | 7个数据位 |
B4800 | 4800波特率 | CS8 | 8个数据位 |
B9600 | 9600波特率 | CSTOPB | 2个停止位(不设为1个停止位) |
B19200 | 19200波特率 | CREAD | 接收使能 |
B38400 | 38400波特率 | PARENB PARODD | 校验位使能使用奇校验而不适用偶校验 |
B57600 | 57600波特率 | HUPCL | 最后关闭时挂线(放弃DTR) |
B115200 | 115200波特率 | CLOCAL | 本地连接(不改变端口所有者) |
EXTA | 外部时钟率 | LOBLK | 块作业控制输出 |
EXTB | 外部时钟率 | CNET_ CTSRTS | 硬件流控制使能 |
在这里,对c_cflag成员不能直接对其初始化,而要将其通过“与”、“或”操作使用其中某些选项。
c_iflag支持的常量名称:
符号 | 定义 | 符号 | 定义 |
---|---|---|---|
INPCK | 奇偶校验使能 | IGNBRK | 忽略中断情况 |
IGNPAR | 忽略奇偶校验错误的字符 | BRKINT | 当发生中断时发送SIGINT信号 |
PARMRK | 对奇偶校验错误做出标记 | INLCR | 将NL映射到CR(新行符->回车符) |
ISTRIP | 去掉第8位,将所有接收到的字符裁剪为7比特 | IGNCR | 忽略CR(忽略回车符) |
IXON | 启动出口硬件流控 | ICRNL | 将CR映射到NL(回车符->新行符) |
IXOFF | 启动入口硬件流控 | IUCLC | 将高位情况映射到低位情况 |
IXANY | 允许字符重新启动流控 | IMAXBEL | 当输入太长时恢复ECHO |
c_cc支持的常量名称:
符号 | 定义 | 符号 | 定义 |
---|---|---|---|
VINTR | 中断控制,对应键为Ctrl + C | VEOL | 位于行尾,对应键为 Carriage return (CR) |
VQUIT | 退出操作,对应键为Ctrl + Z | VEOL2 | 位于第二行尾,对应键为 Line feed (LF) |
VERASE | 删除操作,对应键为Backspace (BS) | VMIN | 指定了最少读取的字符数 |
VKILL | 删除行,对应键为Ctrl + U | VTIME | 指定了读取每个字符的等待时间 |
VEOF | 位于文件结尾,对应键为Ctrl + D |
下面详细讲解设置串口属性的基本流程:
①保存原先串口配置
首先,为了安全起见和以后调试程序方便,可以先保存原先串口的配置,在这里可以使用函数 tcgetattr(fd, &oldtio)。该函数得到与fd指向对象的相关参数,并将它们保存于oldtio引用的termios结构体中,该函数可以测试配置是否正确,该串口是否可用等。若调用成功,函数返回值为0,若调用失败,函数返回值为-1。
使用方式如下:
struct termios newtio, oldtio;if(tcgetattr(fd, &oldtio) == -1){perror("tcgetattr error");exit(-1);}bzero(&newtio, sizeof(struct termios));//->memset(&newtio, 0, sizeof(struct termios));
②激活选项有 CLOCAL 和 CREAD
CLOCAL 和 CREAD 用于本地连接和接收使能,因此,首先要通过位掩码的方式激活这两项:
newtio.c_cflag |= CLOCAL | CREAD;
③设置波特率
设置波特率有专门的函数,用户不能直接通过位掩码方式来操作。设置波特率的主要函数有cfsetispeed和cfsetospeed,这两个函数的使用很简单,如下所示:
cfsetispeed(&newio, B115200);cfsetospeed(&newio, B115200);
一般情况下,用户需将输入/输出的波特率设为一样。函数成功返回0,失败返回-1。
④设置字符大小
与设置波特率不同,设置字符大小并没有现成可用的函数,需要用位掩码。一般首先去除数据位中的位掩码,再重新按要求设置,如下所示:
options.c_cflag &= ~CSIZE;options.c_cflag |= CS8;
⑤设置奇偶校验位
设置奇偶校验位需要用到两个termios中的成员:c_cflag和c_iflag。首先要激活c_cflag中的检验位使能标志PARENB和是否进行偶校验,同时还要激活c_iflag中的奇偶校验使能。
如使能奇校验时,代码如下:
newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.iflag |= (INPCK | ISTRIP);
如使能偶校验时,代码如下:
newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;newtio.c_iflag |= (INPCK | ISTRIP);
无奇偶校验时,代码如下:
newtio.c_cflag &= ~PARENB;
⑥设置停止位
设置停止位是通过激活c_cflag中的CSTOPB而实现的。若停止位为1,则清除CSTOPB,若停止位为2,则激活CSTOPB。下面是停止位为1时的代码:
newtio.c_cflag &= ~CSTOPB;
⑦设置最少字符和等待时间
在对接收字符和等待时间没有特殊要求的情况下,可以将其设置为0,如下所示:
newtio.c_cc[VTIME] = 0;newtio.c_cc[VMIN] = 0;
⑧处理要写入的引用对象
由于串口在重新设置之后,在此之前写入的引用对象要重新处理,这时就可以调用函数tcflush(fd, queue_selector) 来处理要写入引用的对象。对于尚未传输的数据,或者收到的但尚未读取的数据,其处理方式取决于queue_selector的值。
这里,queue_selector可能的取值有以下几种:
TCIFLUSH:刷新收到的数据但是不读;
TCOFLUSH:刷新写入的数据但是不传送;
TCIOFLUSH:同时刷新收到的数据但是不读,并刷新写入的数据但是不传送
示例:
tcflush(fd, TCIFLUSH);
⑨激活配置
在完成全部串口配置之后,要激活刚才的配置并使配置生效,这里用到的函数是tcsetattr,这个函数原型是tcsetattr(fd, OPTION, &newtio);;成功返回0,失败返回-1.
这里的newtio就是termios类型的变量,OPTION可能的取值有以下三种:
TCSANOW:改变的配置立即生效;
TCSADRAIN:改变的配置在所有写入fd的输出都结束后生效;
TCSAFLUSH:改变的配置在所有写入fd引用对象的输出都被结束后生效,所有已接受但未读入的输入都在改变发生前丢弃。
(3)读写串口
读写串口就是把串口当文件去读写。
发送数据:
char buffer[1024];int nwrite = write(fd, buffer 1024);
读取串口数据:
char buff[1024];while((nread = read(fd, buff, 1024)) > 0){buff[nread + 1] = '\0';printf("\n%s", buff);}
(4)关闭串口就是关闭文件
close(fd);
十六、日志
许多应用程序需要记录它们的活动,系统程序经常需要向控制台或日志文件写消息。这些消息可能指示错误、警告或者是与系统有关的一般消息。通常是在/var/log目录下的messages中包含了系统信息。通过syslog可以向系统的日志发送日志信息。
函数原型如下:
#include <syslog.h>void syslog(int priority, const char * message, arguments...);
对于priority有如下几个常见的:
LOG_ EMERG 紧急情况
LOG_ ALERT 高优先级故障(如:数据库崩溃)
LOG_ CRIT 严重错误(如:硬件错误)
LOG_ ERR 错误
LOG_ WARNING 警告
LOG_ NOTICE 需要注意的特殊情况
LOG_ INFO 一般信息
LOG_ DEBUG 调试信息(写不到message里面)
利用tail -10 /var/log/message可以查看,还可以通过函数openlog来改变日志信息的表达方式,openlog的原型如下:
#include <syslog>void openlog(const char * ident, int logopt, int facility);void closelog(void);
它可以设置一个字符串ident,该字符串会添加在日志信息的前面。可以通过它来指明是哪个应用程序创建了这条信息。facility的值为LOG_USER。logpot参数对后续syslog调用的行为进行配置,如下:
LOG_PID 在日志信息中包含进程标识符,这是系统分配给每个进程的一个唯一值LOG_CONS 如果信息不能被记录到日志文件中,就把他们发送到控制台
总结
嵌入式Linux系统编程学习之九基于文件描述符的文件操作相关推荐
- 嵌入式Linux系统编程学习之二常用命令
嵌入式Linux系统编程学习之二常用命令 文章目录 嵌入式Linux系统编程学习之二常用命令 前言 一.常用命令 1.su(用户切换) 2.useradd(添加用户) 3.passwd(修改密码) 4 ...
- 嵌入式Linux系统编程学习之一目录结构
嵌入式Linux系统编程学习之一目录结构 文章目录 嵌入式Linux系统编程学习之一目录结构 前言 一.Linux目录结构 前言 Linux目录结构 一.Linux目录结构 /bin:存放Linux的 ...
- 嵌入式Linux系统编程学习之三十三网络相关概念
文章目录 一.网络相关名词的概念 1.套接口 2.端口号 3.IP 地址 二.Socket 概念 三.Socket 类型 四.Socket 的信息数据结构 五.数据存储优先顺序的转换 六.地址转格式转 ...
- 嵌入式Linux系统编程学习之十二守护进程
文章目录 前言 一.守护进程的特性 二.daemon 进程的编程规则 1.创建子进程,父进程退出 2.在子进程中创建新会话 前言 daemon 运行在后台,也称作"后台服务进程" ...
- 嵌入式Linux系统编程学习之十一Linux进程的创建与控制
文章目录 一.fork函数 二.进程的终止 三.wait 和 waitpid 函数 四.exec 函数族 五.system 函数 六.popen 函数 总结 一.fork函数 fork 函数原型: # ...
- 嵌入式Linux系统编程学习之二十六多线程概述
文章目录 一.多线程概述 二.线程分类 三.线程创建的Linux实现 一.多线程概述 进程是系统中程序执行和资源分配的基本单位.每个进程有自己的数据段.代码段和堆栈段,这就造成进程在进行切换等操作 ...
- 嵌入式Linux系统编程学习之四Shell编程
文章目录 前言 一.Shell变量 1.创建变量 2.引用变量 3.删除变量 4.设置变量为只读变量 5.显示上条命令执行结果或函数返回值 6.表达式计算 二.标准变量 三.特殊变量 四.变量赋值 五 ...
- 嵌入式Linux系统编程学习之二十无名管道(PIPE)
管道是 Linux 进程间通信的一种方式,如命令 ps -ef | grep ntp . 无名管道的特点包括: 只能在亲缘关系进程间通信(父子或兄弟): 半双工(固定的读端和固定的写端): 它 ...
- 嵌入式Linux系统编程学习之八基于文件指针的文件操作
文章目录 前言 一.文件的创建.打开与关闭 二.读写文件 三.文件定位 四.标准输入/输出流 五.目录操作 前言 对文件进行操作有打开文件.关闭文件.读写文件. 文件指针:每打开一个文件,就返 ...
最新文章
- 34补1-2_3 HA Cluster基础及heartbeat实现HA
- shell脚本的执行方式及区别
- linux 新建用户配置文件 /etc/login.defs 简介
- Linux登陆Mariadb数据库,Mariadb数据库的远程连接(centos 7+ Navicat)
- centOS6.5下openfoam-2.4.0安装及并行实现
- gRPC快速入门记录
- A20 文件系统预装APK
- 罗永浩回归!将开秋季旧机发布会:与iPhone 12同一天
- 车牌识别算法及其MATLAB实现
- 【官方文档】Fluent Bit 安装在 Kubernetes
- 甲基化芯片Beta值意义详解,以及minfi包使用
- 被子植物353个单拷贝核基因靶向捕获探针 Plant Universal — Angiosperms 353
- MTK 连接CCT 连不上 出现CDC Serial驱动无法识别
- BootStrap Table:表格参数
- 【计算机网络 12,Java视频下载
- VBA提高篇_12_1 VBA判断奇偶数的方法
- 微信公众平台相关信息
- 苹果AppStore应用商店生存之道:国内iOS开发者创业经验分享(三)
- 2020年全国高新企业认证名单统计
- kegg 上ko号对应的通路数据
热门文章
- python转str类型的列表为list格式
- c语言链表输出header中的乱码,大家帮忙看看这段代码,最后一个链表输出后总带一些乱码?请教...
- 腾讯这套SpringMvc面试题你了解多少?(面试必备)
- idea 中新建Servlet
- 20172315 2017-2018-2 《程序设计与数据结构》第九周学习总结
- Vue(八)发送跨域请求
- LeeCode-------Letter Combinations of a Phone Number 解法
- Perl重命名当前目录下的文件
- 批处理打开和关闭oracle11g 服务
- BZOJ 4143: [AMPPZ2014]The Lawyer( sort )