Linux应用程序常用函数
在
linux应用程序设计中,通常不需要去了底层的驱动是怎么实现的,只需要利用系统提供的接口函数,就可以去访问底层设备。这篇文档是记录自己学习《嵌入式
linux应用程序开发详解》,其中主要介绍在应用程序中一些常用函数,并通过实例来介绍这些函数的用法。其中主要介绍了基于文件
IO的编程,例如打开关闭,读写等等;进程及进程间通信;最后介绍了基于网络的的
socket编程。
一:文件 I/O编程
通过上面的系统框图,可以发现,应用程序有两种方式进行系统调用,直接调用和通过C库函数调用,也即是利用linux系统提供的文件IO和标准C库函函数的IO来进行系统调用。先说说基于linux系统的文件IO操作。
㈠不带缓存的文件IO操作
1. open()函数
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
所需头文件:
#include
#include
#include
函数原型:intopen(const char *pathname,int flags,int perms)
参数:
pathname:被打开的文件名(可包括路径名如
"dev/ttyS0")
flags:文件打开方式
,
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以读写方式打开文件
O_CREAT:如果改文件不存在,就创建一个新的文件,并用第三个参数为其设置权限
O_EXCL:如果使用
O_CREAT时文件存在,则返回错误消息。这一参数可测试文件是否存在。此时
open是原子操作,防止多个进程同时创建同一个文件
O_NOCTTY:使用本参数时,若文件为终端,那么该终端不会成为调用
open()的那个进程的控制终端
O_TRUNC:若文件已经存在,那么会删除文件中的全部原有数据,并且设置文件大小为
0
O_APPEND:以添加方式打开文件,在打开文件的同时,文件指针指向文件的末尾,即将写入的数据添加到文件的末尾
O_NONBLOCK: 如果
pathname指的是一个
FIFO、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的
I/O操作设置非阻塞方式。
O_SYNC:使每次
write都等到物理
I/O操作完成。
O_RSYNC:read
等待所有写入同一区域的写操作完成后再进行
在open()函数中,falgs参数可以通过“|”组合构成,但前3个标准常量(O_RDONLY,O_WRONLY,和O_RDWR)不能互相组合。
perms:被打开文件的存取权限,可以用两种方法表示,可以用一组宏定义:
S_I(R/W/X)(USR/GRP/OTH),其中
R/W/X表示读写执行权限,
USR/GRP/OTH分别表示文件的所有者
/文件所属组
/其他用户
,如
S_IRUUR|S_IWUUR|S_IXUUR,(
-rex------)
,也可用八进制
800表示同样的权限
返回值:
成功:返回文件描述符
失败:返回-1
2. close()函数
功能描述:用于关闭一个被打开的的文件
所需头文件:#include
函数原型:intclose(int fd)
参数:fd文件描述符
函数返回值:0成功,-1出错
3. read()函数
功能描述:从文件读取数据。
所需头文件:#include
函数原型:ssize_tread(int fd, void *buf, size_t count);
参数:
fd:将要读取数据的文件描述词。
buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。
count:表示调用一次
read操作,应该读多少数量的字符。
返回值:返回所读取的字节数;0(读到EOF);-1(出错)。
以下几种情况会导致读取到的字节数小于count
:
A. 读取普通文件时,读到文件末尾还不够
count
字节。例如:如果文件只有
30 字节,而我们想读取
100
字节,那么实际读到的只有30
字节,read 函数返回 30
。此时再使用 read 函数作用于这个文件会导致 read
返回 0 。
B. 从终端设备(
terminal device)读取时,一般情况下每次只能读取一行。
C. 从网络读取时,网络缓存可能导致读取的字节数小于
count字节。
D. 读取
pipe
或者
FIFO 时,
pipe
或
FIFO 里的字节数可能小于
count
。
E. 从面向记录(
record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
F. 在读取了部分数据时被信号中断。
读操作始于 cfo
。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
4. write()函数
功能描述:向文件写入数据。
所需头文件:#include
函数原型:ssize_twrite(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)
功能:write
函数向 filedes 中写入count
字节数据,数据来源为 buf 。返回值一般总是等于 count,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。
对于普通文件,写操作始于cfo
。如果打开文件时使用了O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo
增加,增量为实际写入的字节数。
5. lseek()函数
功能描述:用于在指定的文件描述符中将将文件指针定位到相应位置。
所需头文件:#include,#include
函数原型:off_tlseek(int fd, off_t offset,int whence);
参数:
fd;文件描述符
offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)
whence:
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:当前位置为指针的位置,新位置为当前位置加上偏移量
SEEK_END:当前位置为文件的结尾,新位置为文件大小加上偏移量的大小
返回值:
成功:返回当前位移
失败:返回-1
㈡标准IO操作
下面的这图能很好的反映,用户通过文件IO和标准IO进行系统调用的区别。
标准IO操作:标准I/O是ANSIC建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。
文件IO操作:称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。
首先:两者一个显著的不同点在于,标准I/O默认采用了缓冲机制,比如调用fopen函数,不仅打开一个文件,而且建立了一个缓冲区(读写模式下将建立两个缓冲区),还创建了一个包含文件和缓冲区相关数据的数据结构。低级I/O一般没有采用缓冲,需要自己创建缓冲区,不过其实在linix或unix系统中,都是有使用称为内核缓冲的技术用于提高效率,读写调用是在内核缓冲区和进程缓冲区之间进行的数据复制。
其次从操作的设备上来区分,文件I/O主要针对文件操作,读写硬盘等,它操作的是文件描述符,标准I/O针对的是控制台,打印输出到屏幕等,它操作的是字符流。对于不同设备得特性不一样,必须有不同api访问才最高效。
标准IO的一些常用函数Fopen(),fclose(),fread(),fwrite();
㈢如何避免竞争
上面只是简单介绍文件操作的基本操作,如果当多个用户共同使用和操作一个文件时,就回导致对共享资源的的竞争,该怎么解决竞争呢?Linux最常用的办法就是给文件上锁。文件锁又包括建议性锁和强制性锁。实现上锁的方法有lock和fcntl,lock用于对文件施加建议性锁,而fcntl用于对文件施加强制性锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁,记录锁可以分为读取锁和写入锁,其中读取锁为共享锁,写入锁为互斥锁。
int fcntl(int fd,int cmd,struct flock * lock);
fcntl()用来操作
文件描述符的一些特性。参数
fd代表欲设置的文件描述词,参数
cmd代表欲操作的指令。
有以下几种情况:
F_DUPFD用来查找大于或等于参数
arg的最小且仍未使用的文件描述词,并且复制参数
fd的文件描述词。执行成功则返回新复制的文件描述词。请参考
dup2()。
F_GETFD取得
close-on-exec旗标。若此旗标的
FD_CLOEXEC位为
0,代表在调用
exec()相关函数时文件将不会关闭。
F_SETFD 设置
close-on-exec
旗标。该旗标以参数
arg 的
FD_CLOEXEC位决定。
F_GETFL
取得文件描述词状态旗标,此旗标为
open()的参数
flags。
F_SETFL 设置文件描述词状态旗标,参数
arg为新旗标,但只允许
O_APPEND、
O_NONBLOCK和
O_ASYNC位的改变,其他位的改变将不受影响。
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时
flcok
结构的
l_type 值必须是
F_RDLCK、
F_WRLCK或
F_UNLCK。如果无法建立锁定,则返回
-1,错误代码为
EACCES
或
EAGAIN。
F_SETLKW F_SETLK
作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回
-1,错误代码为
EINTR。
参数lock指针为flock
结构指针,定义如下
struct flcok
{
short intl_type;
short intl_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
};
l_type 有三种状态
:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式
:
SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。
返回值成功则返回0,若有错误则返回-1,错误原因存于errno.
Fcntl实例
#include
#include
#include
#include
#include
#include
/*利用
fcntl创建记录锁
*/
voidlock_set(int fd,int type)
{
struct flock lock;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
lock.l_start = 0;
while(1)
{
lock.l_type = type;
/*根据不同的
type给文件上锁
*/
if((fcntl(fd, F_SETLK,&lock)) == 0)
{
if(lock.l_type ==F_RDLCK)
printf("read lock is set by %d
", getpid());
else if(lock.l_type ==F_WRLCK )
printf("write lock is set by %d
", getpid());
else if(lock.l_type ==F_UNLCK)
printf("release lock is set by %d
", getpid());
return;
}
/*判断文件是否可以上锁
*/
fcntl(fd, F_GETLK, &lock);
if(lock.l_type != F_UNLCK)
{
if(lock.l_type ==F_RDLCK)/*该文件已有读入琐
*/
printf("read lock is set by %d
", lock.l_pid);
else if(lock.l_type ==F_WRLCK )/*该文件已有写入锁
*/
printf("write lock already set by %d
", lock.l_pid);
}
}
}
int main(void)
{
int fd;
fd =open("/home/fany/application/io/hello.c", O_RDWR, 0666);
if(fd < 0)
{
perror("openfailed
");
exit(1);
}
/*给文件加锁
*/
lock_set(fd,F_RDLCK);
getchar();
/*给文件解锁
*/
lock_set(fd,F_UNLCK);
getchar();
close(fd);
}
㈣select函数实例
Fcntl函数解决了因争抢共享资源而发生的竞态,而
select函数是处理
IO复用的一个高效的方法。他可以设置每一个所关心的文件描述符的条件,希望等待时间,从
select返回时,内核会通知用户已准备好的文件描述符的数量,已准备好的条件等。
int select(intmaxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
先说明两个结构体:
第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set
*),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int
,fd_set* )。一会儿举例说明。
第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。
具体解释select的参数:
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加
1,不能错!在
Windows中这个参数的值无所谓,可以设置不正确。
fd_set *readfds是指向
fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,
select就会返回一个大于
0的值,表示有文件可读,如果没有可读的文件,则根据
timeout参数再判断是否超时,若超出
timeout的时间,
select返回
0,若发生错误返回负值。可以传入
NULL值,表示不关心任何文件的读变化。
fd_set *writefds是指向
fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,
select就会返回一个大于
0的值,表示有文件可写,如果没有可写的文件,则根据
timeout参数再判断是否超时,若超出
timeout的时间,
select返回
0,若发生错误返回负值。可以传入
NULL值,表示不关心任何文件的写变化。
fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval*timeout是
select的超时时间,这个参数至关重要,它可以使
select处于三种状态,第一,若将
NULL以形参传入,即不传入时间结构,就是将
select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为
0秒
0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回
0,有变化返回一个正值;第三,
timeout的值大于
0,这就是等待的超时时间,即
select在
timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
返回值:负值:select错误正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件
Select应用实例
#include
#include
#include
#include
#include
int main(void)
{
intfd0,fd1;
charbuf[7];
inti,rc,maxfd;
fd_setinset0,inset1;
structtimeval tv;
/*按一定权限打开
hello1.c*/
if((fd0= open("/home/fany/application/io/hello1.c", O_RDWR | O_CREAT, 0666))< 0)
perror("open hello1.c failed
");
/*按一定权限打开
hello2.c*/
if((fd1= open("/home/fany/application/io/hello2.c", O_RDWR | O_CREAT, 0666))< 0)
perror("open hello1.c failed
");
if((rc= write(fd0, "Hello!