网络编程高级主题
fcntl()/mmap()
1、fcntl
#include <sys/types.h>
#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 fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
fcntl()针对(文件)描述符提供控制.参数fd 是被参数cmd操作(如下面的描述)的描述符.
针对cmd的值,fcntl能够接受第三个参数int arg
针对cmd的值,fcntl能够接受第三个参数int arg
1. 参数fd
参数fd代表欲设置的文件描述词。
2. 参数cmd
参数cmd代表打算操作的指令。
有以下几种情况:
F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd的文件描述词。执行成功则返回新复制的文件描述词。新描述符与fd共享同一文件表项,但是新描述符有它自己的一套文件描述符标志,其中FD_CLOEXEC文件描述符标志被清除。请参考dup2()。
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。
3. 参数lock指针
参数lock指针为flock 结构指针,定义如下
struct flock
{
short int l_type;
short int l_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()用来操作文件描述符的一些特性。fcntl 不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl还能对文件的某一记录进行上锁,也就是记录锁。
int flags;
/* 设置为非阻塞*/
if (fcntl(socket_descriptor, F_SETFL, flags | O_NONBLOCK) < 0)
{
/* Handle error */
}
/* 设置为阻塞 */
if ((flags = fcntl(sock_descriptor, F_GETFL, 0)) < 0)
{
/* Handle error */
}
//设置为阻塞模式
if(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL & (~O_NONBLOCK))) < 0)
if(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL & (~O_NONBLOCK))) < 0)
注:
1、由exec启动的新进程继承了原进程的许多特性。在原进程中已打开的文件描述符在新进程中仍将保持打开,除非它们的“执行时关闭标志”被置位,此时任何在原进程中已打开的目录流都将在新进程中被关闭
2、exec函数可以吧当前进程替换为一个新进程,新的程序启动后,原来的程序就不再运行了。假如exec在一个线程中执行,原来的进程同样也不再继续运行了。
实例:
#include <stdio.h> #include <stdlib.h> #include <sys/shm.h> #include <unistd.h> #include <pthread.h> #include <string.h> typedef enum LANGUAGE { LANGUAGE_MAX }LANGUAGE_E; void *thread_function(void *arg); char message[]="hello world"; void *thread_function(void *arg) { printf("thread_function is running.arg was %s\n",(char *)arg); sleep(3); strcpy(message,"bye"); //execlp("ps","ps","ax",0); system("mkdir 11"); pthread_exit("thankyou for the cpu time\n"); } int main() { int i=0,res=0; pthread_t a_thread; void *thread_result; res=pthread_create(&a_thread,NULL,thread_function,(void *)message); if(res!=0){ perror("thread creation failed"); exit(EXIT_FAILURE); } printf("waiting for thread to finish...\n"); res=pthread_join(a_thread,&thread_result); if(res!=0){ perror("thread_join failed\n"); exit(EXIT_FAILURE); } printf("thread joined,it returned %s\n",(char *)thread_result); printf("message is now %s\n",message); exit(EXIT_SUCCESS); while(1) { printf("i is %d,,,sizeof(LANGUAGE_E) is %d,,SHM_RDONLY is %d,,SHM_RND is %d\n",i++,sizeof(LANGUAGE_E),SHM_RDONLY,SHM_RND); break; } printf("Running ps with execlp\n"); //execlp("ps","ps","ax",0); i=system("mkdir 11"); printf("i is %d,done\n",i); return 0; }
2、mmap
mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *start, size_t length);
mmap()必须以PAGE_SIZE()为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射。
start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。
length:映射区的长度。
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_DENYWRITE //这个标志被忽略。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
MAP_FILE //兼容标志,被忽略。
MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
offset:被映射对象内容的起点。
EACCES:访问出错
EAGAIN:文件已被锁定,或者太多的内存已被锁定
EBADF:fd不是有效的文件描述词
EINVAL:一个或者多个参数无效
ENFILE:已达到系统对打开文件的限制
ENODEV:指定文件所在的文件系统不支持内存映射
ENOMEM:内存不足,或者进程已超出最大内存映射数量
EPERM:权能不足,操作不允许
ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
SIGSEGV:试着向只读区写入
SIGBUS:试着访问不属于进程的内存区
系统调用mmap()用于共享内存的两种方式:
(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:
fd=open(name, flag, mode);
if(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。
(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。
fd=open(name, flag, mode);
if(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。
(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。
范例1:
#include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> typedef struct{ char name[4]; int age; }people; main(int argc, char** argv) // map a normal file as shared mem: { int fd,i; people *p_map; people aa={0}; char temp; fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777); //fwrite(&aa,sizeof(aa),10, lseek(fd,sizeof(people)*10-1,SEEK_SET); write(fd,"",1); p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 ); close( fd ); temp = 'a'; for(i=0; i<10; i++) { temp += 1; memcpy( ( *(p_map+i) ).name, &temp,2 ); ( *(p_map+i) ).age = 20+i; } printf(" initialize over \n "); sleep(10); munmap( p_map, sizeof(people)*10 ); printf( "umap ok \n" ); }
范例2:
#include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> typedef struct{ char name[4]; int age; }people; main(int argc, char** argv) // map a normal file as shared mem: { int fd,i; people *p_map; fd=open( argv[1],O_CREAT|O_RDWR,00777 ); p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); for(i = 0;i<10;i++) { printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age ); } munmap( p_map,sizeof(people)*10 ); }
3、ftruncate
表头文件 #include<unistd.h>
定义函数 int ftruncate(int fd,off_t length);
函数说明
ftruncate()会将参数fd指定的文件大小改为参数length指定的大小。
参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。
如果原来的文件大小比参数length大,则超过的部分会被删去。
返回值
执行成功则返回0,失败返回-1,错误原因存于errno。
EBADF 参数fd文件描述词为无效的或该文件已关闭。
EINVAL 参数fd 为一socket 并非文件,或是该文件并非以写入模式打开。
#include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #ifndef FAIL #define FAIL -1 #endif typedef struct{ char name[4]; int age; }people; int main(int argc, char** argv) // map a normal file as shared mem: { int fd,i,res=0,nMummap = -1,nfstat = -1,nflenth=0; char * mapped_mam, *p_mam ,*p_lastmam; struct stat sb; printf( "EBADF is %d,, EACCES is %d,, EAGAIN is %d,, EINVAL is %d,, \n",EBADF,EACCES,EAGAIN,EINVAL); fd=open(argv[1],O_CREAT|O_RDWR,00777); //fd = open(argv[1], O_RDWR, S_IRUSR | S_IWUSR); nfstat = fstat(fd, &sb); if(nfstat != 0) { printf("fstat err %s%d", __FILE__, __LINE__); } nflenth = (int)sb.st_size; printf( "nflenth is %d,,\n" ,nflenth); lseek(fd, 0, SEEK_SET); mapped_mam = (char *)mmap(0, 80, PROT_READ | PROT_WRITE, MAP_PRIVATE,fd, 0); if(mapped_mam == MAP_FAILED) { printf("MAP_FAILED\n"); close(fd); return FAIL; } p_mam = mapped_mam + 40; write(fd, p_mam,40); //注:若不进行内存映射去截断文件长度的话,最后文件当中的长度大小虽然符合,但是其中的内容却为NULL。暂还不知道原因是什么 //当进行映射后,方可将文件内容截断前移 res = ftruncate(fd,sizeof(people)*5); close(fd); nMummap = munmap(mapped_mam, 80); if(nMummap == -1) { printf("munmap err %s%d", __FILE__, __LINE__); return FAIL; } printf( "res is %d,,\n" ,res); return 0; }
一个奔跑的程序员