70多个函数,Linux系统调用一网打尽
站在那儿别动,伙计!瞧你那一脸邋遢的络腮胡……那两根吊带,还有那洋洋得意的表情。十足一副高人一等的UNIX电脑用户的样子!
——《UNIX环境高级编程》
引言
本文简单记录了一下5helter在使用Linux系统调用中常用的函数,仅供参考,由于编辑本文时未使用markdown导致文章格式混乱,敬请谅解。本文持续更新中
正文
判断文件是否存在
int access(const char* pathname,int FLAG)
调用时FLAG使用宏:F_OK 目录存在,R_OK文件存在,返回非零数创建文件夹
bool mkdir(const char* pathname,int limit)
limit是文件夹权限,pathname是绝对路径打开文件夹
DIR *opendir(const char *__name)
打开失败返回空读取目录中的文件
dirent *readdir(DIR *__dirp)
dirent类保存有目录信息,注意这个函数是每调用一次返回一个目录中的文件,直到没有文件时返回空获取文件信息
int stat(const char *restrict __file, stat *restrict __buf)
stat类保存文件信息,返回值int为空时调用文件失败或权限不足等重命名文件
int rename(const char *__old, const char *__new)
这个函数相当于cp命令删除文件或目录
int remove(const char *__filename)
成功返回0,失败返回-1设置文件时间
int utime(const char __file, const utimbuf *__file_times)
失败返回0
其中utimbuf类型如下:
struct utimbuf
{
time_t actime; / Access time. /
time_t modtime; / Modification time. */
};重命名文件
int rename(const char *__old, const char *__new)
注意__new的目录如果不存在,将返回失败将UTC时间转化为本地时间(年月日结构体)
tm *localtime_r(const time_t *restrict __timer, tm *restrict __tp)
通过引用返回值
注意
sttm.tm_year=sttm.tm_year+1900; // tm.tm_year成员要加上1900。
sttm.tm_mon++; // sttm.tm_mon成员是从0开始的,要加1。断开socket连接
int close(int __fd)
返回值:
失败返回负,但是我们通常不会关系通过主机名获取ip地址
hostent *gethostbyname(const char __name)
struct hostent 是用于存储主机名和其相关的网络地址信息的结构体成员如下:
h_name: 官方的主机名。
h_aliases: 主机名的别名列表。
h_addrtype: 地址类型,通常是 AF_INET 表示IPv4。
h_length: 地址的长度。
h_addr: 主机地址的列表。对于IPv4,这通常作为(char)使用,长度为h_length,网络序创建套接字
int socket(int __domain, int __type, int __protocol)
参数解释:
__domain填AF_INET表示IPV4
__type 填的参数可以分为两部分
第一部分填SOCK_STREAM或者SOCK_DGRAM分别表示流式SOCKET(tcp协议)和数据包SOCKET
第二部分可以选择或上SOCK_CLOECEX和SOCK_NONBLOCK
第三个参数填0(使用默认),也可以填IPPROTO_TCP/IPPROTO_UDP
返回值:
函数返回套接字句柄,也是一个io文件,失败时返回-1把本地序列转换为网络序
uint16_t htons(uint16_t __hostshort)
端口号通过这个转换
注意INADDR_ANY宏需要用htonl函数转换流式socket连接到主机
int connect(int __fd, const sockaddr *__addr, socklen_t __len)
如果是tcp(流式socket基本上都是tcp,几乎没有列外)
__fd socket句柄
__addr 填充好的sockaddr_in对象
_t __len 填入sizeof(sockaddr_in)即可接收报文函数
ssize_t recv(int __fd, void *__buf, size_t __n, int __flags)
参数解释:
这里只解释一下 __flags,其他参数同编号69,70两个函数
__flags 一般填0即可
返回读取到的字符数量,出错返回-1发送报文函数
ssize_t send(int __fd, const void *__buf, size_t __n, int __flags)
参数情况同16,
返回值:
发送的字符数量,出错返回-1设置socket选项
int setsockopt(int __fd, int __level, int __optname, const void *__optval, socklen_t __optlen)
参数解释:
__fd socket描述符
__level 网络层协议可选的选项有SOL_SOCKET(tcp/udp 通用)IPPROTO_TCP IPPROTO_UDP
__optname 在该层要设置的属性,比如SO_REUSEADDR,SO_REUSEPORT,SO_KEEPALIVE(SOCK级别),TCP_NODELAY(tcp级别)
__optval 选项设置的值,一般填值为1的整型变量,表示打开;当然填别的值的,根据__optname需要的参数决定
_t __optlen __optval的字节数,填sizeof(__optval)
返回值:
为负如果失败
说明:
此函数在在服务器端使用将socket绑定到监听端口
int bind(int __fd, const sockaddr __addr, socklen_t __len)
__addr是要监听的sockaddr_in,至少设置sin_family(协议) sin_addr.s_addr(地址) sin_port(端口) 三个成员
注意将sockaddr_in强转为sockaddr*;
__len传入sizeof(sockaddr_in)
此函数在在服务器端使用,设置失败返回负数socket监听
int listen(int __fd, int __n)
__n最大排队长度,超过可能会被拒绝连接
成功返回0,失败返回-1阻塞等待客户端连接
int accept(int __fd, sockaddr *restrict __addr, socklen_t *restrict __addr_len)
restrict __addr 传入一个sockaddr_in,指针,内容将被函数设置
restrict __addr_len socklen_t类型是一个整数,填入sizeof(sockaddr_in)即可
返回与客户端连接的Socket描述符创建子进程
int fork()
返回值:
主进程返回子进程的进程ID,是一个大于零的数,子进程返回0,出错返回-1
说明:
在Linux中,不同进程的socket id/文件描述符 并不是共享的;也就是说,即使socket id/文件描述符 是相同的,在不同的进程中代表的连接也是不同的;
默认情况下fork()函数不仅会复制socket id/文件描述符 到子进程,也会新建对应连接,从子进程到对应的目标。(底层使用了引用计数器实现,socket id/文件描述符会记录拥有这个连接的进程个数,close函数
是将个数减一,只有当计数为0时才会被关闭),如果创建Socket时指定了SOCK_CLOEXEC,那么子进程会关闭这个Sock回收子进程资源
pid_t wait(int* status)
阻塞直到有子进程退出,status被设置为退出子进程状态,使用一些宏可转化为字符串查看
此函数在signal(SIGHID,SIG_DFL)下生效
signal(SIGHID,SIG_IGN)下失效,不过情况下系统自动回收资源,父进程繁忙时,将SIGHIDSIGHID绑定到处理函数,如需要;设置环境变量
int setenv(const char *__name, const char *__value, int __replace)
下面以设置环境字符集为例子,程序字符集与数据库不兼容将导致数据库数据乱码;
第三个参数填1时,如果存在这个环境变量就用新值替代
第二个参数是值,字符集可以是 “Simplified Chinese_China.AL32UTF8”
第一个参数是环境变量名, 更改字符集填 “NLS_LANG”
这个设置只对当前用户的本次登录生效,重启后失效(写在.bashrc或者.bash_profile中才能永久生效)select模型
int select(int nfds, fd_set* restrict readfds,fd_set restrict writefds, fd_set restrict errorfds,struct timeval* restrict timeout);//restrict 关键字表示指针需要指向一个左值地址
参数解释:
fd_set* readfds
传递给select()一个设置好的fd_set对象的地址,用于监视读事件,若不监视填NULL; fd_set解释如下:
socket集合,大小为1024位,作为位图使用;C语言使用以下四个函数操作位图:
void FD_ZERO(fd_set *fdset); //初始化位图,将1024个位全部置为0
void FD_SET(int fd, fd_set fdset); //fd是描述符,把对应socket加入集合中( 其实是把对应位置置一,因此fd<1024)
void FD_CLR(int fd, fd_set fdset); //把fd从位图中删除
int FD_ISSET(int fd, fd_set fdset); //判断fd是否在位图中,不在返回0,否则返回大于零的数
struct timeval timeout
超时时间,在超时时间内,若没有发生任何事件,select()函数返回,不再阻塞进程,如果填NULL或者0表示永远等待
timeval结构体成员 int tv_sec 秒 int tv_usec 毫秒
select事件分为读事件和写事件
读事件包括
1.监听
2.读缓存中有数据
3.收到close()申请
写事件包括
1.写缓冲区可写入数据
int nfds
传递给select函数位图的实际大小
fd_set writefds
传递给select()一个设置好的fd_set对象的地址,用于监视读事件,若不监视填NULL;
fd_set errorfds
传递给select()一个设置好的fd_set对象的地址,用于监视异常事件,若不监视填NULL;
函数返回值:
小于0:调用select失败,errno被设置(可能得原因有内存不足,参数错误,描述符异常,收到信号终止)
等于0;select超时
大于0;返回已经发生的事件数量,readfds/writefds被修 修改的具体方式是,没有事件的socket fd位被清零
其他说明:
select函数判断是否有读写事件发生是通过检测socket的发送或接收缓冲区
应用经验:
水平触发:用select检测的socket发生了事件,立即返回
实际上可以在有事件触发时,先等一段时间再次检测后处理
由于每次select()函数都要更改位图,因此应该传递给select()一个临时位图每次循环前从复制源位图复制poll模型
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
参数解释:
pollfd* fds
传入poll位图;
poll模型使用数组作为位图,格式是:pollfd <位图名>[]; constexpr number可取1024,2048等,但不要超过10000;
其中pollfd 结构如下:
struct pollfd {
int fd; /* file descriptor /
short events; / requested events /
short revents; / returned events */
};
参数解释:
fd socket描述符,poll认为-1代表没有socket
events 选择要监听的事件,取值可以是POLLIN POLLOUT POLLDHUP POLLPRI(带外数据) POLLERR POLLHUP(挂起事件)
revents 由poll()函数设置,revents与事件宏相与可以判断事件是否发生
nfds_t nfds
传入一个整数,认为是监视socket的最大下标
int timeout
超时事件,单位是毫秒
返回值:
小于0:调用poll失败,errno被设置(可能得原因有内存不足,参数错误,描述符异常,收到信号终止)
等于0;poll超时
大于0;返回已经发生的事件数量, 位图中每个pollfd 的revents被修改
开发经验:
位图的下标和fd的值一般相同,这样符合变成规范epoll模型
int epoll_create(int size);
参数解释:
int size
已弃用,填任意大于零的书
返回值:
返回一个epoll文件描述符(句柄),是一个整数
控制epoll句柄的函数:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
参数解释:
int epfd :
epoll句柄
int op :
对epoll进行的操作
可选的选项有:EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD
int fd :
填入要监听的socket
epoll_event* event :
epoll_event 是epoll事件类型,定义如下:
struct epoll_event
{
uint32_t events; /* Epoll events /
epoll_data_t data; / User data variable /
};
其中epoll_data定义如下:
typedef union epoll_data
{
void ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
一般epoll_data只需使用fd,并且将其设置为socket描述符即可;这里填的是什么,epoll_wait函数返回的就是什么;
epoll_data_t data 设置为要监听的事件,可以是EPOLLIN EPOLLOUT EPOLLDHUP EPOLLPRI(带外数据) EPOLLERR EPOLLHUP(挂起事件)
注意socket断开时EPOLL_CTL_DEL不是必须的,epoll会自动卸下
使用epoll的函数:
int epoll_wait(int epfd, struct epoll_event events,int maxevents, int timeout);
参数解释:
int epfd :
epoll句柄
epoll_event events:
传入一个epoll_event数组,内容将被本函数设置
int maxevents:
传入events的数组长度
int timeout:
超时时间
返回值:
小于0:调用epoll失败,errno被设置(可能得原因有内存不足,参数错误,描述符异常,收到信号终止)
等于0;epoll超时
大于0;返回已经发生的事件数量,即设置events中元素的个数
关闭epoll的函数
void close(int epfd);
epoll用完后需关闭设置文件描述符状态
文件描述符(文键句柄)有很多状态,这些状态也由整数表示,并且可以使用 | 做添加运算
int fcntl(int fd, int cmd, … /* arg */);
用于对文件描述符执行各种操作。它提供了一种在运行时控制文件描述符的方法,包括改变文件状态、设置文件描述符标志、以及执行各种其他操作
fd:表示文件描述符,
cmd:表示要执行的操作命令,是一个整数值。例如获取状态 F_GETFL 设置状态F_SETFL
arg:这是一个可选的参数,它取决于 cmd 参数的具体操作类型。根据不同的命令,arg 可能是一个整数、一个结构体指针,或者其他类型的数据。
下面演示将socket文件描述符设置为非阻塞状态的例子:
int setnonblocking(int fd)
{
int flags;
// 获取fd的状态。
if ((flags=fcntl(fd,F_GETFL,0))==-1)
flags = 0;
return fcntl(fd,F_SETFL,flags|O_NONBLOCK);
}
非阻塞socket说明:非阻塞socket的connect recv send accept函数都不再阻塞,但是connect函数一定会返回失败这是返回的错误代码是EINPROGRESS;要通过检查socket是否可写检测连接是否成功
在io复用模型中,connect recv send accept都必须是非阻塞的,任何阻塞操作交给poll或者epoll进行;代码如下:
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
{
if (errno!=EINPROGRESS)
{
printf(“connect(%s:%s) failed.\n”,argv[1],argv[2]); close(sockfd); return -1;
}
}
pollfd fds;
fds.fd=sockfd;
fds.events=POLLOUT;
poll(&fds,1,-1); //负一表示一直等待
if (fds.revents==POLLOUT)
printf(“connect ok.\n”);
else
printf(“connect failed.\n”);
同样的问题还有非阻塞的accept,recv函数,使用下面的代码解决
while (accept(listensock,0,0)==-1)
{
if (errno!=EAGAIN) //EAGAIN表示缓冲区中无内容(send函数是被填满)
perror(“accept:”); return -1;
else
break;
}
当然直接交给poll或者epoll等待accept和recv没有这么多事epoll边缘触发模式
在向epoll句柄中添加epoll_event* event时,如果对齐成员events的赋值 | 上EPOLLET 可设置为边缘触发,边缘触发不再通过检测缓冲区有无内容判断有无事件发生,而是通过等待期间缓冲区的变化判断
边缘触发模式一定要用循环处理事件,一次性把缓冲区处理干净;定时器
unsigned alarm(unsigned seconds);
经过unsigned seconds向进程发送一个SIGALRM信号
返回值:
返回上一个alarm()调用剩余的秒数,如果没有返回0
alarm函数实际上是对计时器句柄的封装,创建按定时器句柄并用epoll监视alarm句柄代码如下:
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//把定时器加入epoll。
int tfd=timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK|TFD_CLOEXEC); // 创建timerfd。
struct itimerspec timeout; // 定时时间的数据结构。
memset(&timeout,0,sizeof(struct itimerspec));
timeout.it_value.tv_sec = 10; // 定时时间为10秒。
timeout.it_value.tv_nsec = 0;
timerfd_settime(tfd,0,&timeout,0); // 开始计时。alarm(10)
ev.data.fd=tfd; // 为定时器准备事件。
ev.events=EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,tfd,&ev); // 把定时器fd加入epoll。
////////////////////////////////////////////////////////////////////////////////////////////////////////////
在epoll模型中,为了为了更新进程心跳或者清理空闲的客户端socket,一般会监视一个计时器事件,注意计时器句柄也是单次的,使用以下函数操控
int timerfd_create(int clockid, int flags);
int timerfd_settime(int fd, int flags,
const struct itimerspec *new_value,
struct itimerspec *old_value);
int timerfd_gettime(int fd, struct itimerspec *curr_value);
此外epoll还监视信号和信号集,代码如下
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//把信号加入epoll。
sigset_t sigset; // 创建信号集。
sigemptyset(&sigset); // 初始化(清空)信号集。
sigaddset(&sigset, SIGINT); // 把SIGINT信号加入信号集。
sigaddset(&sigset, SIGTERM); // 把SIGTERM信号加入信号集。
sigprocmask(SIG_BLOCK, &sigset, 0); // 对当前进程屏蔽信号集(当前程将收不到信号集中的信号)。
int sigfd=signalfd(-1, &sigset, 0); // 创建信号集的fd。
ev.data.fd = sigfd; // 为信号集的fd准备事件。
ev.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,sigfd,&ev); // 把信号集fd加入epoll。
////////////////////////////////////////////////////////////////////////////////////////////////////////////线程通信
线程通信有三种方式
1.发送信号,epoll可以监视信号
2.使用tcp通信
3.使用管道通信,管道通信是一种在本地上简易的通信方式,相比tcp管道
int pipe(int *__pipedes)
解释:
此函数用于创建管道,管道是用户进程间通信是一个单向通信通道,数据只能从一个进程的输出端写入,然后从另一个进程的输入端读取
参数解释:
传入一个大小为2的整型数组
__pipedes[0]用于读取数据,__pipedes[1]用于写入数据
返回值:
如果执行成功,pipe函数返回0
如果执行失败,返回-1,并设置errno来指示错误类型获取UTC时间
time_t time(time_t* now)
__now__被设置errno==EINTR错误
这个错误是,阻塞的系统调用(wait,accpt等)过程中,程序收到软中断导致的,防止这个错误的方法包括closeallioandsignal(true)获取微秒精度时间
UTC时间,即time_t是精确到秒的,要获得微秒级时间需要使用下面的函数
int gettimeofday(struct timeval*tv, struct timezone *tz);
timezone *tz是需要设置的时区,填nullptr即可
timeval类型
struct timeval{
long int tv_sec; // 秒数
long int tv_usec; // 微秒数
}clock()函数
获取程序消耗时钟数
发现clock()是程序从启动到函数调用占用CPU的时间。这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数
使用方法如下
int i = 100000000;
clock_t start,finish; //定义开始,结束变量
start = clock();//初始化
while( i– );
finish = clock();//初始化结束时间
double duration = (double)(finish - start) / CLOCKS_PER_SEC;//转换浮点型
printf( “%lf seconds\n”, duration );
该方法计算出程序所使用的时间创建命名管道
int mkfifo(const char *pathname, mode_t mode);
匿名管道pipe只能由于父子进程或者线程之间通信
创建匿名管道可以用于进程间通信
*pathname 普通的路径名,也就是创建后 FIFO 的名字
mode ⽂件的权限,
与打开普通⽂件的 open() 函数中的 mode 参数相同。(0666).创建socket
int socket(int domain, int type, int protocal)
domain 参数⽤来指定协议族,⽐如 AF_INET,AF_LOCAL(本地进程间通信)
type 参数SOCK_STREAM和SOCK_DGRAM
protocal填0创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
*thread穿入线程地址
attr 传入需要设置的pthread属性,可以填nullptr
void *(*start_routine) (void ) 传入线程函数指针,这个函数返回值和参数只能是void,参数含义需要自己确定
arg 传入的线程函数的参数
返回值:小于零返回失败回收非分离线程资源
int pthread_join(pthread_t thread, void **retval);
thread 传入线程id
retval 线程函数的返回值
返回值:线程函数的返回状态,自然退出返回0,取消返回负一,出错返回其他负数线程函数返回
void pthread_exit(void* retval)
线程函数不可以用exit函数退出,这个函数等价于 return (void*)retval初始化线程属性
int pthread_attr_init(pthread_attr_t *attr);
初始化一个线程属性结构体
返回值为负如果失败
注意:应先初始化线程属性,再pthread_create创建线程
成功: 0; 失败: 错误号.设置线程属性
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
attr填要设置的属性
detachstate 填PTHREAD_CREATE_DATACHED可以设置为分离态如果设置⼀个线程为分离线程创建,⽽这个线程运⾏⼜⾮常快,它很可能在pthread_create函数返回之前就终⽌
了,它终⽌以后就可能将线程号和系统资源移交给其他的线程使⽤,这样调⽤pthread_create的线程就得到
了错误的线程号
要避免这种情况可以采取⼀定的同步措施,最简单的⽅法之⼀是可以在 被创建的线程⾥ 调⽤pthread_cond_timedwait函数,
让这个线程等待⼀会⼉,留出⾜够的时间让函数pthread_create返回设置线程为分离态
int phtread_detach(pthread_t thread);
这个函数可以在运行中设置,前面的方法只能在创建线程前设置获取本线程id
pthread_t pthread_self()取消线程
int pthread_cancel(pthread_t thread)线程清理
void pthread_cleanup_push(void (*routine)(void *),void *arg);
线程退出前调用此函数进行善后,压入一个在线程退出前执行pthread_cleanup_pop就会执行对应的栈顶函数
void pthread_cleanup_pop(int execute);
pthread_cleanup_push和pthread_cleanup_pop在线程函数中必须成对出现,否则编译出错
execute函数
填0弹出不执行,填大于零的数弹出并执行
线程退出前,压到栈中的清理函数会被执行,无论其是否执行到pthread_cleanup_pop向发送信号
int pthread_kill(pthread_t thread, int sig)
参数解释:
thread:
线程id
sig:
发送的信号
返回值:
为负如果失败
说明:
使用这个函数直接对线程发信号,会使对应线程中断
向进程发送的信号并不会中断子线程,主线程和子线程共享信号处理函数,你可在任何线程调用signal函数改变信号处理器
因此可以说线程缺少对信号的支持,用信号进行线程间通信是困难的
主线程(进程)向子线程发送信号:获取系统线程id
int syscall(int number, …);
说明:
在多线程中,pthread_self()函数获得的线程号是pthread库对线程的编号,而不是Linux系统对线程的编号。这个线程号只在本进程可用在别的进程中可能会被重用
pthread_create()返回的线程号,使用top命令是查不到的,top显示的是Linux的线程号,syscall(SYS_gettid)返回的线程号,在系统中任意一个时刻是唯一的,
置得注意到是,在主线程中调用getpid()得到的值与主线程中调用syscall(SYS_gettid)得到的值一致,返回值类型是pid_t
该函数的一个典型用法是
获取的方法数是syscall(SYS_gettid),销毁线程
int pthread_attr_destroy(pthread_attr_t *attr);
attr 线程属性结构体
成功: 0; 失败: 错误号获取线程状态
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
从attr中获取对应属性设置栈空间
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);获取栈空间
int pthread_attr_getstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);socket对
int socketpair(int domain, int type, int protocol, int sv[2]);eventfd线程间通信
int eventfd(unsigned int initval, int flags);
这个是用于线程间通信的新方法,效率高于管道,使用方法完全等同于普通文件描述符初始化信号量(posix型,一般用于线程通信)
sint sem_init(sem_t *sem, int pshared, unsigned int value);
参数解释:
sem_t *sem 需要初始化的信号量指针,左值,一般直接创建然后取地址传递即可
int pshared 传入0代表在线程间使用,一般是全局变量即可,传入1代表线程间使用,必须在共享内存区创建sem_t,具体方法后面说
unsigned int value 初始值,一般的初始值设置为零可以用于确定某操作必须限于某操作;设置为1相当于互斥锁信号量P操作
sem_post(sem_t *sem)
这个不解释,参考计算机操作系统课程信号量V操作
sem_wait(sem_t *sem)
这个不解释,参考计算机操作系统课程存储映射IO(PosiX)
void *mmap(void addr, size_t length, int prot, int flags,int fd, off_t offset);
参数解释:
addr 映射到的偏移地址,一般填NULL由系统设置即可
length 需要映射的长度(一个正数),一般需要小于文件大小,如果大小不对,将收到SIGBUS错误信号
prot 文件属性,可选PROT_WRITE,PROT_READ,PROT_EXEC,在MAP_SHARED模式下打开的文件描述符必须设置为大于这里的权限,否则会出参数错误,而MAP_PRIVATE则不需要,因为不会写道文件里
flags MAP_SHARED和MAP_PRIVATE,多进程用前者,后者不会把内存中的值写到文件中,MAP_ANONYMOUS用于创建匿名映射,此时fd设置为-1
fd 打开的文件描述符,改函数建立映射后可立即关闭fd
offset 偏移量,必须为4k的整数倍,填零即可
返回值:
失败时返回(void)-1;
用法:
可以进行父子进程通信,需要设置MAP_SHARED
也可以用于非亲缘关系的进程间通信,映射到同一个文件即可,显然不能创建匿名的
unix和系统中有两个特殊的文件/dev/zreo和/dev/null前者可以读无限个空洞,后者可以无限写入设置文件偏移量
off_t lseek(int fd, off_t offset, int whence);
参数解释:
fd 打开的文件描述符
offset 偏移量字节为单位
whence 开始计算偏移的起始位置,可选SEEK_SET,SEEK_END,SEEK_CUR
返回值:
失败时返回-1;成功时返回较文件起始位置的偏移总量
用法:处理用于设置偏移,还常用于创建文件空洞,
如果总偏移位置大于了文件大小,不足的部分将由空洞补充,在空洞后面写入一个字符可以是\0,此次空洞将被保存到文件中,空洞不能被读到,但是可以用于存储映射IO打开文件
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
这个函数不解释修改文件大小
int ftruncate(int fd, off_t length)
源文件长度如果不足,系统将用空洞填充释放映射区
int munmap(void *addr, size_t length);
参数解释:
addr起始地址
length大小
返回值:
成功返回0,失败返回-1
用法:
释放映射区时内存数据才会真正被写到文件,如果要提前写入调用msync(2)函数,否则写入将被从文件上撤销创建或者打开共享内存/在交换分区上的内存映射创建
int shmget(key_t key, size_t size, int shmflg);
参数解释:
key 用于标识共享内存,共享内存编号不可以相同,一般由程序员设置一个十六进制数
size 创建共享内存的大小
shmflg 访问权限同open的mode参数
返回值:
失败时返回-1;返回共享内存的标识
用法:
也叫systemV型内存映射,特点是不能在文件系统中直接看到文件实体,只能通过ipcs -sa 命令查看,这种文件可以防止运行中文件被误删
使用ipcrm shm [shmid]删除共享内存
我这里给出一个典型用法:
int shmid=shmget((key_t)0X5005,1024,0640|IPC_CREATE);//创建shmid得角色类似于fd,key_t的角色类似于文件名连接共享内存到程序内存空间
void *shmat(int shmid, const void shmaddr, int shmflg);
参数解释:
shmid 共享内存标识
shmaddr 可以填空,让系统决定起始位置
shmflg 标志位填0即可
返回值:
返回指向映射的第一个地址,出错返回(void)-1从进程中分离共享内存
int shmdt(const void *shmaddr);
参数解释:
shmaddr shmat返回的地址
返回值:
出错返回负一共享内存控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参阅linux程序员手册
我们这里了解一种用法即可
shmctl(shmid,IPC_RMID,0)
用于删除共享内存信号量集创建或者获取(systemV型,可用于多进程)
int semget(key_t key, int nsems, int semflg);
参数解释:
key 用于标识信号量集,信号量编号不可以相同,一般由程序员设置一个十六进制数
nsems 信号量的个数
semflg 同open函数的mode,可以使用IPC_CREAT标记用于创建
返回值:
返回semid,失败时返回-1
PosiX型信号量又叫信号灯一般适用于多线程程序,而systemV型是基于共享内存的,可以在进程间通信
使用ipcs -s命令查看创建的信号量 ipcrm sem可以删除信号量 信号量控制
int semctl(int semid, int semnum, int cmd, …);
参数解释:
semid semget的返回值
semnum 当前要控制信号量集中的第几个信号量,第一个填0,第二个填1
cmd 常用的有两个IPC_RMID,SETVAL,GETVAL分别表示删除信号量,设置初始值,获取信号量当前值
… 一个共用体,结构如下:
union semun {
int val; /* Value for SETVAL */
struct semid_ds buf; / Buffer for IPC_STAT, IPC_SET */
unsigned short array; / Array for GETALL, SETALL */
struct seminfo __buf; / Buffer for IPC_INFO
(Linux-specific) */
};
一般使用它的val成员就可以,表示信号量的初始值
注意这个共用体必须程序员自己定义
返回值:
失败返回-1信号量操作
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数解释:
semid semget的返回值
sops 结构体如下:
struct sembuf
{
short sem_num; //信号量在信号量集中的位置
short sem_post //-1等待,+1发送
short sem_flg //把此标志SEM_UNDO系统将跟踪这个信号量,如果进程结束没有释放信号量,系统将自动释放
//如果用于控制进程顺序,这个字段填零
}
sops可以是一个sembuf的地址也可以是sembuf的数组首地址,即可以同时对多个信号量集操作
nsops 要操作的信号量集数,如果sops是一个sembuf的地址,这个值填1
返回值:
为负如果失败读IO
ssize_t read(int fd, void *buf, size_t count);
参数解释:
fd 文件描述符
buf 读入到的起始位置
count 要读入的字节数
返回值:
ssize_t是一个有符号整数,返回的是成功读取的字节数,读取失败返回负数写IO
ssize_t write(int fd, const void *buf, size_t count);
参数解释:
fd 文件描述符
buf 写入的起始位置
count 要写入的字节数
返回值:
ssize_t是一个有符号整数,返回的是成功写入的字节数,读取失败返回负数从IO读入到非连续空间
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
参数解释:
fd 文件描述符
iov iovec数组,iovec类型用于描述内存空间,结构如下
struct iovec {
void iov_base; / Starting address /
size_t iov_len; / Number of bytes to transfer */
};
成员解释:
iov_base 内存的起始地址
iov_len 内存的长度
iovcnt 参数二中数组长度是多少这里填几
返回值:
函数调用成功时返回读,失败时返回 -1 并设置相应的 errno从非连续空间写入IO
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
返回值:
函数调用成功时返回读,失败时返回 -1 并设置相应的 errno