1、dup和dup2函数
dup和dup2系统调用都可以用来复制文件描述符,在Shell下输入“man dup”可获取它们的函数原型如下:
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd,int newfd);
dup用来复制参数oldfd所指的文件描述符。当复制成功时,返回最小的尚未被使用的文件描述符。若有错误则返回-1,错误代码存入errno中,详细的错误代码说明请参考man手册。返回的新文件描述符和参数oldfd指向同一个文件,共享所有的锁定、读写指针和各项权限或标志位。例如,当利用lseek()对某个文件描述符操作时,另一个文件描述符的读写位置会随着改变。
dup2与dup的区别是dup2可以用参数newfd指定新文件描述符的数值。若newfd已经被程序使用,系统就会将其关闭以释放该文件描述符;若oldfd与newfd相等,则dup2返回newfd,而不关闭它。dup2调用成功,返回新的描述符,出错返回-1。
Shell中的重定向功能(输入重定向“<”和输出重定向“>”)就是通过dup和dup2函数对标准输入和标准输出的操作来实现的。
2、fcntl函数
fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的各种属性,在Shell下输入“man 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);
fcntl的功能依据cmd值的不同而不同,具体有以下几种功能。
(1)F_DUPFD
此时,fcntl的功能与dup一样,可以复制由fd指向的文件描述符。调用成功返回新的文件描述符,失败返回-1,错误代码存入errno中,详细的错误代码说明请参考man手册。从错误代码获取错误描述信息的程序代码请参考例6-2,下同。
(2)F_GETFD
此时,fcntl用来获取文件描述符的close-on-exec标志。调用成功返回标志值,若此标志值的最后一位是0,则该标志没有设置,即意味着在执行exec相关函数后文件描述符仍保持打开。否则在执行exec相关函数时将关闭该文件描述符。失败返回-1。
(3)F_SETFD
此时,fcntl用来设置文件描述符的close-on-exec标志为第三个参数arg的最后一位。成功返回0,失败返回-1。
(4)F_GETFL
此时,fcntl用来获得文件打开的方式。成功返回标志值,失败返回-1。标志值的含义同open系统调用一致。
(5)F_SETFL
此时,fcntl用来设置文件打开的方式为第三个参数arg指定的方式。但是Linux系统只能设置O_APPEND,O_NONBLOCK,O_ASYNC标志,它们的含义也和open系统调用一致。
例6-4
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
/*自定义的错误处理函数*/
void my_err(const char *err_string,int line)
{
fprintf(stderr,"line:%d",line);
perror(err_string);
exit(1);
}
int main()
{
int ret;
int access_mode;
int fd;
if ((fd = open("example_64",O_CREAT|O_TRUNC|O_RDWR,S_IRWXU)) == -1)
{
my_err("open",__LINE__);
}
/*设置文件打开方式*/
if ((ret = fcntl(fd,F_SETFL,O_APPEND)) < 0)
{
my_err("fcntl",__LINE__);
}
/*获取文件打开方式*/
if ((ret = fcntl(fd,F_GETFL,0)) < 0)
{
my_err("fcntl",__LINE__);
}
access_mode = ret & O_ACCMODE;
if (access_mode == O_RDONLY)
{
printf("example_64 access mode:read only");
}
else if (access_mode == O_WRONLY)
{
printf("example_64 access mode:write only");
}
else if (access_mode == O_RDWR)
{
printf("example_64 access mode:read + write");
}
if (ret & O_APPEND)
{
printf(",append");
}
if (ret & O_NONBLOCK)
{
printf(",nonblock");
}
if (ret & O_SYNC)
{
printf(",sync");
}
printf("/n");
return 0;
}
程序说明。
程序中的O_ACCMODE是取得文件打开方式的实际上它的值就是3,做与运算只是为了 取得ret的最后两位的值。
接下来的fcntl函数3种功能都和文件记录锁有关,因此先介绍一下文件锁。当有多个进程同时对某一文件进行操作时,就有可能发生数据的不同步,从而引起错误,该文件的最后状态取决于写该文件的最后一个程序。但是对于有些应用程序,如数据库,有时进程需要确保它正在单独写一个文件。为了向进程提供这种功能,Linux系统提供了记录锁机制。
Linux的文件记录锁能提供非常详细的控制,它能对文件的某一区域进行文件记录锁的控制,当fcntl用于管理文件记录锁的操作时,第三个参数指向一个struct flock *lock的结构:
struct flock
{
short l_type; /*锁的类型*/
short l_whence; /*偏移量的起始位置:SEEK_SET,SEEK_CUR,SEEK_END*/
off_t l_start; /*Starting offset for lock*/
off_t l_len; /*Number of bytes to lock*/
pid_t l_pid; /*锁的属性进程ID*/
};
l_type用来指定是设置共享锁(F_RDLCK,读锁)还是互斥锁(F_WRLCK,写锁)。多个进程在一个给定的字节上可以有一把共享锁的读锁,但是在一个给定字节上的写锁则只能由一个进程单独使用。进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁(锁的不兼容规则)。一个进程只能设置某一个文件区域上的一种锁。如果某一文件区域已经存在文件记录锁了,则如果此时再设置新的锁在该区域的话,旧的锁将会被新的锁取代。
l_whence,l_start和l_len用来确定需要进行文件记录锁操作的区域,含义和lseek的一致。如l_len为0,则表示锁的区域从其起点(由l_start和l_whence决定)开始直至最大可能位置为止。也就是不管添加到该文件中多少数据,它都处于锁的范围。为了锁整个文件,通常的方法是将l_start说明为0,l_whence说明为SEEK_SET,l_len说明为0。
(6)F_SETLK
此时,fcntl系统调用被用来设置或释放锁,当l_type取F_RDLCK或F_WRLCK时,在由l_whence,l_start和l_len指定的区域上设置锁;当l_type取F_UNLCK时则释放锁。如果锁被其他进程占用,则返回-1并设置errno为EACCES或EAGAIN。
需要注意的是,当设置一个共享锁(读锁)时,第一个参数fd所指向的文件必须以可读方式打开;当设置一个互斥锁(写锁)时,第一个参数fd所指向的文件必须以可写方式打开;当设置两种锁时,第一个参数fd所指向的文件必须以可读可写方式打开。当进程结束或文件描述符fd被close系统调用时,锁会自动释放。
(7)F_SETLKW
此时,fcntl的功能与cmd取F_SETLK时类似,不同的是当希望设置的锁因为存在其他而被阻止设置时,该命令会等待相冲突的锁被释放。
(8)F_GETLK
此时,第3个参数lock指向一个希望设置锁的属性的结构,如果锁能被设置,该命令并不真的设置锁,而只是修改lock的l_type域为F_UNLCK,然后返回。如果存在一个或多个锁与希望的锁互相冲突,则fcntl返回其中的一个锁的flock结构。
cmd取(6)、(7)、(8)时,执行成功返回0,当有错误发生时返回-1,错误代码存入errno中,详细的错误代码说明请参考man手册。
Linux系统的文件记录锁默认是建议性的。我们考虑数据库存取例程库,如果数据库中所有函数都以一致的方法处理记录锁。则称使用这些函数存取数据库的任何进程集为合作进程(cooperating process)。如果这些函数是惟一的用来存取数据库的函数,那么它们使用建议性锁是可行的。但是建议性锁并不能阻止对数据库文件有写许可权的任何其他进程写数据库文件。不使用协同一致的方法(数据库存取例程库)来存取数据库的进程是一个非合作进程。强制性锁机制中,内核对每一个open,read和write都要检查调用进程对正在存取的文件是否违背了某一把锁的作用。
例6-5是对锁的应用实例程序,具体应用见程序中的代码注释。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
/*自定义的错误处理函数*/
void my_err(const char *err_string,int line)
{
fprintf(stderr,"line:%d ",line);
perror(err_string);
exit(1);
}
/*锁的设置或释放函数*/
int lock_set(int fd,struct flock *lock)
{
if (fcntl(fd,F_SETLK,lock) == 0)
{ //执行成功
if (lock->l_type == F_RDLCK)
{
printf("set read lock,pid:%d/n",getpid());
}
else if (lock->l_type == F_WRLCK)
{
printf("set write lock,pid:%d/n",getpid());
}
else if (lock->l_type == F_UNLCK)
{
printf("release lock,pid:%d/n",getpid());
}
}
else
{//执行失败
perror("lock operation fail/n");
return -1;
}
return 0;
}
/*测试锁,只有当测试发现参数lock指定的锁能被设置时,返回0*/
int lock_test(int fd,struct flock *lock)
{
if (fcntl(fd,F_GETLK,lock) == 0)
{//执行成功
if (lock->l_type == F_UNLCK)
{//测试发现能按参数lock要求设置锁
printf("lock can be set in fd/n");
return 0;
}
else
{
if (lock->l_type == F_RDLCK)
{
printf("can't set lock,read lock has been set by:%d/n",lock->l_pid);
}
else if (lock->l_type == F_WRLCK)
{
printf("can't set lock,write lock has been set by:%d/n",lock->l_pid);
}
return -2;
}
}
else
{
perror("get incompatible locks fail");
return -1;
}
}
int main()
{
int fd;
int ret;
struct flock lock;
char read_buf[32];
/*打开或创建文件*/
if ((fd = open("example_65",O_CREAT|O_TRUNC|O_RDWR,S_IRWXU)) == -1)
{
my_err("open",__LINE__);
}
if (write(fd,"test lock",10) != 10)
{
my_err("write",__LINE__);
}
/*初始化lock结构*/
memset(&lock,0,sizeof(struct flock));
lock.l_start = SEEK_SET;
lock.l_whence = 0;
lock.l_len = 0;
/*设置读锁*/
lock.l_type = F_RDLCK;
if (lock_test(fd,&lock) == 0)
{//测试可以设置锁
lock.l_type = F_RDLCK;
lock_set(fd,&lock);
}
/*读数据*/
lseek(fd,0,SEEK_SET);
if ((ret = read(fd,read_buf,10)) < 0)
{
my_err("read",__LINE__);
}
read_buf[ret] = '/0';
printf("%s/n",read_buf);
/*等待任意按键*/
getchar();
/*设置写锁*/
lock.l_type = F_WRLCK;
if (lock_test(fd,&lock) == 0)
{//测试可以设置锁
lock.l_type = F_WRLCK;
lock_set(fd,&lock);
}
/*释放锁*/
lock.l_type = F_UNLCK;
lock_set(fd,&lock);
close(fd);
return 0;
}
程序说明。
本程序将锁的设置与测试都写成单独的子函数,程序首先在当前目录下建立了文件example_65,并写入了一个字符test lock到example_65文件中,然后对其进行文件记录锁的操作。
执行结果如下:
$ ./6-5
lock can be set in fd
set read lock,pid:15579
test lock
d
lock can be set in fd
set write lock,pid:15579
release lock,pid:15579
从结果可以看出,程序首先测试能不能在文件上加上读锁,测试结果表明可以在指定文件上设置读锁,于是在文件上设置了读锁;然后从文件中进行读操作,并将读出来的数据打印出来;再测试能不能在文件上加上写锁,测试结果表明可以在指定文件上设置写锁,于是又在文件上设置了写锁。
大家可能要问:前面不是介绍在某一字节上设置了读锁后就不能再设置写锁了吗?为什么这里在设置了读锁后又成功地设置了写锁了呢?这是因为本程序只是一个进程,单个进程在同一字节上只能设置一种锁,新的锁会取代旧的锁。锁的不兼容规则是针对于多个进程之间的。
为了演示锁的不兼容规则,仍旧用上面的程序,但是程序分别在不同的终端执行。
先在某一终端执行程序(假设为进程1),结果如下:
$ ./6-5
lock can be set in fd
set read lock,pid:16551
test lock
这时不进行任何按键操作,程序一直阻塞着等待用户的按键输入。再打开一个新的终端执行程序(假设为进程2),结果如下:
$ ./6-5
lock can be set in fd
set read lock,pid:16558
test lock
从进程2的结果可以看出,虽然进程1已在指定文件上设置了读锁,但是进程2仍旧能从文件上正确地读出数据,可见Linux的文件记录默认是建议性锁而不是强制性锁。
切换到第二个终端,输入任意按键,结果如下:
can't set lock,read lock has been set by:16551
release lock,pid:16558
由结果可见,由于进程1在文件上设置了读锁,按照锁的不兼容规则,进程2只设置成功了读锁,而不能设置写锁了。
(9)F_GETOWN
此时,返回当前接收SIGIO或SIGURG信号的进程ID或进程组,进程组ID以负值返回。
(10)F_SETOWN
此时,设置进程或进程组接收SIGIO和SIGURG信号,进程组ID以负值指定,进程ID用正值指定。
(11)F_GETSIG
此时,可以在输入输出时获得发送的信号。
(12)F_SETSIG
此时,设置在输入输出时发送的信号。
3、ioctl函数
ioctl系统调用通常用来控制设备,不能用上面介绍的其他函数进行的控制操作都可以用ioctl来进行,在Shell下输入“man ioctl”可获取其函数原型如下:
#include <sys/ioctl.h>
int ioctl(int fd,int request,...);
ioctl用来控制特殊设备文件的属性,第一个参数fd必须是一个已经打开的文件描述符,第三个参数一般为char *argp,它随第二个参数request的不同而不同。参数request决定了参数argp是向ioctl传递数据还是从ioctl获取数据。
例6-6采用获取网络设备的信息,在编写网络相关程序时可能会用到。在学习了网络编程后就可以完全理解本程序了。
例6-6
//示例ioctl的使用
//本程序修改自网络上的程序,版权归原作者所有
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
unsigned char g_eth_name[16];
unsigned char g_macaddr[6];
unsigned char g_subnetmask;
unsigned char g_ipaddr;
unsigned char g_broadcast_ipaddr;
/*初始化网络,获得当前网络设备的信息*/
void init_net(void)
{
int i;
int sock;
struct sockaddr_in sin;
struct ifreq ifr;
sock = socket(AF_INET,SOCK_DGRAM,0);
if (sock == -1)
{
perror("socket");
}
strcpy(g_eth_name,"eth0");
strcpy(ifr.ifr_name,g_eth_name);
printf("eth name:/t%s/n",g_eth_name);
/*获取并打印网卡地址*/
if (ioctl(sock,SIOCGIFHWADDR,&ifr) < 0)
{
perror("ioctl");
}
memcpy(g_macaddr,ifr.ifr_hwaddr.sa_data,6);
printf("local mac:/t");
for (i=0;i<5;i++)
{
printf("%.2x:",g_macaddr[i]);
}
printf("%.2x:/n",g_macaddr[i]);
//获取并打印IP地址
if (ioctl(sock,SIOCGIFADDR,&ifr) < 0)
{
perror("ioctl");
}
memcpy(&sin,&ifr.ifr_addr,sizeof(sin));
g_ipaddr = sin.sin_addr.s_addr;
printf("local eth0:/t%s/n",inet_ntoa(sin.sin_addr));
//获取并打印广播地址
if (ioctl(sock,SIOCGIFBRDADDR,&ifr) < 0)
{
perror("ioctl");
}
memcpy(&sin,&ifr.ifr_addr,sizeof(sin));
g_broadcast_ipaddr = sin.sin_addr.s_addr;
printf("broadcast:/t%s/n",inet_ntoa(sin.sin_addr));
//获取并打印子网掩码
if (ioctl(sock,SIOCGIFNETMASK,&ifr) < 0)
{
perror("ioctl");
}
memcpy(&sin,&ifr.ifr_addr,sizeof(sin));
g_subnetmask = sin.sin_addr.s_addr;
printf("subnetmask:/t%s/n",inet_ntoa(sin.sin_addr));
close(sock);
}
int main()
{
/*initialize...*/
init_net();
/*do something*/
return 0;
}
程序说明。
程序先创建一个用于网络通信的套接字,然后利用ioctl对其操作,获取网络信息。程序中的函数net_ntoa用来将网络地址转换成字符串形式。本程序在计算机上执行的结果如下:
$ ./6-6
eth name: eth0
local mac: 00:0c:29:1c:38:28:
local eth0: 192.168.0.14
broadcast: 192.168.0.255
subnetmask: 255.255.255.0
转载于:https://www.cnblogs.com/jasonliu/archive/2012/08/21/2649099.html