本文仅作个人积累。待功成,重新分类排版。
章14起
1:非阻塞IO:发出open/read/write等IO操作,并使这些操作不会永远阻塞。当不能完成时,会立即出错返回。
1,非阻塞的两种标志方式:指定标志:O_NONBLOCK。
2,非阻塞语义:文件状态标志的更改影响同一文件表项的所有用户,但与通过其他文件表项对同一设备的访问无关。(关联于文件表项)
2:记录锁功能(字节范围锁):一个进程正在读/修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一个文件区。
1,它可以只锁住文件的一段区域。控制上更为精确。
2,fcnt1记录锁。
int fcnt1(int fd, int cmd, struct flock *flockptr ); struct flock { short l_type; // F_RDLCK(共享读锁), F_WRLCK(独占写锁), F_UNLCK(解锁一个区域) short l_whence; // 加/解锁区域的起始字节偏移量。可选值:SEEK_SET, SEEK_CUR, SEEK_END off_t l_start; // 加/解锁区域的起始字节偏移量。 off_t l_len; // 区域字节长度.等于0时,表示可以添加为最大偏移量,而不是0。 pid_t l_pid; // 当前进程ID(cmd == F_GETLK 时) }
1)对于记录锁,参数cmd是: F_GETLK(获取锁状态), F_SETLK(设置锁), F_SETLKW(设置锁的阻塞版,W==wait)。
2)参数flockptr时记录锁的一个结构体。
3)共享读和独占写的控制模式和读写锁类似。
4)单个进程在同一区域设置第二把锁时,会覆盖之前的锁。
5)加读锁时,描述符必须时读打开;加写锁时,必须是写打开。
6)使用参数cmd 获取/设置锁 的操作都不是原子操作。
3,锁的隐含继承和释放。
1)当一个进程终止时,它建立的锁全部释放。当一个描述符关闭时,进程通过描述符设置的锁也会释放。
2)由fork产生的子进程不继承父进程设置的锁。
3)执行exec后,新程序可以继承原执行程序的锁(和上条似乎存在矛盾)。
4,合作进程:一个库的所有函数都以一致的方法处理记录锁,则称使用这些函数访问数据库的进程集为合作进程。
5,强制性锁:该锁会让内核见车每一个IO函数,验证调用进程是否违背了正在访问的文件上的某一把锁。
1)打开方式:对特定文件打开其设置组ID位,关闭组执行位(无法理解)。
2)建议锁的含义?
3)强制性锁存在缺陷,是可以避开的。
3:轮询:一段时间,调用一次期望的进程/函数。
1,多任务系统中,尽量避免使用此方法。
4:异步IO(asynchronous IO)。
1,机制:当描述符准备号可以进程IO时,发送一个信号通知进程。
2,存在问题。
1)仅当描述符引用中断设备或网络时,它才能起作用。
2)这种信号对每个进程都只有一个。
5:IO多路转接(IO multiplexing)。
1,机制:构造一个包含描述符的列表,然后调用一个函数。当描述符中的一个准备好进程IO操作时,该函数才返回。
2,相关函数。
<sys/select.h> 1 多路转接的查询函数。 int select( int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr ); // 1 输入:描述符,描述符条件(读/写/异常),等待时间。 // 2 输出:描述符总量,已准备好的描述符条件 // 3 参数tvptr: ==NULL 永远等待, ==0 不等待, !=0 等待具体时间。无const,无法保证不被修改。。 // 4 fd_set类型参数(写/读/异常条件): 当参数==NULL时,表示不关心此参数。 // 5 参数maxfdp1:最大文件描述符编号+1. // 6 返回值:出错-1,0 表示没准备好的描述符, >0 已准备好的描述符数之和。 int pselect( int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, const struct timespec *restrict tsptr, const sigset_t *restrict sigmask ); // 1 提供高精度,并超时值无法改变。 // 2 可使用 信号屏蔽字。 int poll( struct pollfd fdarray[], nfds_t nfds, int timeout ); // 1 将我们感兴趣的描述符写进 pollfd 数组中。 // 2 参数 nfds:指定数组元素个数。 // 3 参数timeout:-1 永远等待,0 不等待,>0 等待具体时间。 2 数据类型 fd_set 的处理函数/宏(取决于实现成函数,还是宏)。 int FD_ISSET( int fd, fd_set *fdset ); // 指定位是否已打开。 void FD_CLR( int fd, fd_set *fdset ); // 清除某一个位。 void FD_SET( int fd, fd_set *fdset ); // 设置某一个位。 void FD_ZERO( fd_set *fdset ); // 所有位设置为0。 // 1 fd_set类型中,一个文件描述符占据一位。通常来说 位上置1 表示条件满足(不确定具体系统实现)。 3 数据类型 pollfd。 struct pollfd { int fd; // file decriptor to check, or <0 to ignore. short events; // events of interest on fd. short revents; // events that occurred on fd. } // 1 如果使用此结构,需要直到具体的event返回值的含义
3,系统并不主动告知任何信息。需要我们使用函数取查询文件描述符。
6:BSD派生系统中,异步IO是信号SIGIO 和 SIGURG 的组合。
1,SIGIO:通用异步IO信号。
2,SIGURG:通知进程网络连接上的带外数据已经到达。
7:POSIX异步IO:为不同类型的文件进行异步IO提供例一套一致的方法。
1,相关函数。
1 AIO控制块基本结构(具体系统可在标准上添加) struct aiocb { int aio_fildes; // file descriptor off_t aio_offset; // file offset for IO volatile void *aio_buf; // buffer for IO size_t aio_nbytes; // number of bytes to transfer int aio_reqprio; // priority struct sigevent aio_sigevent; // signal information int aio_opcode; // operation for list IO } 2 基本读写操作。 int aio_read( struct aiocb * aiocb ); int aio_write( struct aiocb * aiocb ); 3 强制等待的异步操作直接写入。 int aio_fsync( int op, struct aiocb *aiocb ); // !!!具体功能未完全确认!!! // 1 op参数有两个选择:O_DSYNC---fdatasync, O_SYNC---fsync 4 获取一个操作完成状态。 int aio_error( const struct aiocb *aiocb ); // 1 返回值:0 成功,-1失败 errno中保存错误信息,EINPROGRESS 操作等待中, 5 如果成功,获取异步操作返回值。 ssize_t aio_return( const struct aiocb *aiocb ); // 1 调用一次后,系统就清除返回值。 6 进程只剩异步操作未完成,可以通过此函数阻塞进程。直到异步操作完成。 int aio_suspend( const struct aiocb *const list[], int nent, const struct timespec *timeout ); 7 取消等待的异步操作。 int aio_cancel( int fd, struct aiocb *aiocb ); // 仅发出取消命令,并不代表一定取消。 // 1 返回值:AIO_ALLDONE 所有操作已完成,不需要取消, AIO_CANCELED 成功, AIO_NOTCANCELED 最少有一个操作没被取消, -1 调用失败,错误保存在errno 8 提交一个 AIO控制块的 列表 int lio_listio( int mode, struct aiocb *restrict const list[ restrict ], int nent, struct sigevent *restrict sigev ); // 1 参数mode:LIO_WAIT, LIO_NOWAIT
2,异步IO操作必须显式的指定偏移量。异步IO接口并不影响由操作系统维护的文件偏移量。
1)使用追加模式时,aio_offset字段会被系统忽略。
8:多种读写函数。
1,相关函数。
1 读/写 多个缓冲区 ssize_t readv( int fd, const struct iovec *iov, int iovcnt ); ssize_t writev( int fd, const struct iovec *iov, int iovcnt ); // 1 参数iovcnt:读取缓冲区的个数。 2 按照需求 读/写 N个字节的数据 ssize_t readn( int fd, void *buf, size_t nbytes ); ssize_t writen( int fd, void *buf, size_t nbytes );
9:应当用尽量少的系统调用次数。
10:储存映射IO。
1,相关函数。
1 将给定文件映射到储存区域。 void *nmap( void *addr, size_t len, int prot, int flag, int fd, off_t off ); // 1 参数addr:指定映射区域的起始地址。设置为0得到最大的可移植性。 // 2 参数prot:储存区的权限(读/写/执行) // 3 参数flag:MAP_FIXED,MAP_SHARED,MAP_PRIVATE. 2 更改映射权限。 int mprotect( void *addr, size_t len, int prot ); 3 将储存区数据写入被映射文件。 int msync( void *addr, size_t len, int flags ); // 1 参数flags:MS_ASYNC, MS_SYNC 4 解除映射区。 int munmap( void *addr, size_t len ); // 调用此函数,不会使储存区数据写入文件
2,机制:将一个磁盘文件映射到储存空间中的一段缓冲区上。
11:进程通信基础(interProcess Communication, IPC)
12:管道。
1,缺陷。
1)部分系统支持全双工(不确定linux)
2)管道只能在具有公共祖先的两个进程之间使用。
2,相关函数。
1 创建管道。 int pipe( int fd[2] ); // 1 fd[0]为读 fd[1]为写。fd[1]的输出是fd[0]的输入。 2 创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell命令,等待命令终止。 // !!!需要进一步了解者两个函数原理和使用机制!!! FILE *popen( cosnt char *cmdstring, const char *type ); // fork --> exec and cmd --> return a ptr of IO. int pclose( FILE *fp ); // 适用于简单的过滤器程序
3,协同进程:一个过滤程序既产生某个过滤程序的输入,又读取该过滤程序的输出。
13:FIFO:命名管道。
1,作用:能够使 不相关的进程 进行通信。
2,相关函数。
#include <sys/stat.h> 1 创建FIFO int mkfifo( const char *path, mode_t mode ); int mkfifoat( int fd, const char *path, mode_t mode ); // 1 一般情况下,都是阻塞到读写开始为止。但设置非阻塞时,会立即返回-1,errno设置ENXIO。
3,一些注意点。
1)FIFO路径名存在于文件系统中。
4,一些用途。
1)shell命令使用FIFO将数据从一条管道传送到另外一条。无需创建中间文件。
2)C/S模式中,FIFO用作汇聚点,在客户进程和服务器进程之间传递数据。
14:XSI IPC
1,相关函数。
1 通过路径名+项目ID产生一个KEY。 key_t ftok( const char *path, int id ); // 1 参数path必须引用现有文件,参数id只使用低8位。 2 ipc_perm 权限和所有者. struct ipc_perm { uid_t uid; // owner‘s effective user id. gid_t gid; // owner‘s effective group id. uid_t cuid; // creator‘s effective user id. gid_t cgid; // creator‘s effective group id. mode_t mode; // access modes } // 此为最小结构。具体实现 可添加成员 3 消息队列的信息结构 msqid_ds。 struct msqid_ds { struct ipc_perm msg_perm; // see section. msgqnum_t msg_qnum; // # of messages on queue. msglen_t msg_qbytes; // max # of bytes on queue. pid_t msg_lspid; // pid of last msgsnd() pid_t msg_lrpid; // pid of last msgrcv() time_t msg_stime; // last-msgsnd() time time_t msg_rtime; // last-msgrcv() time time_t msg_ctime; // last-change time } 4 打开/创建 一个队列。 int msgget( key_t key, int flag ); 5 对 队列 执行多种操作。 int msgctl( int msqid, int cmd, struct msqid_ds *buf ); // 1 参数cmd:IPC_STAT, IPC_SET, IPC_RMID 6 将数据放到消息队列中。 int msgsnd( int msqid, cosnt void *ptr, size_t nbytes, int flag ); // 1 参数ptr:指向mymesg结构。 7 从队列取消息。 ssize_t megrcv( int msqid, void *ptr, size_t nbytes, long type, int flag ); // 1 参数type:>0时,以非 先进先出 的次序 获取消息。 8 获取一个信号量。 int semget( key_t key, int nsems, int flag ); 9 多种信号量操作。 int semctl( int semid, int semnum, int cmd, .../* union semun arg */ ); // 1 参数cmd:IPC_STAT,IPC_SET,IPC_RMID,GETVAL... 10 自动执行信号量集合上的操作数组。 int semop( int semid, struct sembuf semoparray[], size_t nops ); // 具有原子性,或者执行所有,或者全部不执行。 struct sembuf { unsigned short sum_num; // member # in set (0, 1, ..., nsems-1) short sem_op; // operation(negative, 0, or pasitive ) short sem_flg; // IPC_NOWAIT, SEM_UNDO } 11 内核为每个共享储存段维护一个结构。 struct shmid_ds { struct ipc_perm shm_perm; // see section size_t shm_segsz; // size of segment in bytes pid_t shm_lpid; // pid of last shmop pid_t shm_cpid; // pid of creator shmatt_t shm_nattch; // number of current attaches time_t shm_atime; // last-attach time time_t shm_dtime; // last-detach time time_t shm_ctime; // last-change time ... } 12 获得一个共享储存标示符。 int shmget( key_t key, size_t size, int flag ); // 1 参数size:字节为单位。通常为系统页长的整倍数。 13 对共享存储 执行多种操作。 int shmctl( int shmid, int cmd, struct shmid_ds *buf ); // 1 参数cmd:IPC_STAT,IPC_SET,IPC_RMID,SHM_LOCK,SHM_UNLOCK 14 将共享存储连接到进程中。 void shmat( int shmid, cosnt void *addr, int flag ); 15 进程和共享存储的分离操作(不删除共享存储)。 int shmdt( const void *addr );
2,有三种:消息队列,信号量,共享储存器。
3,使用 非负整数 的标识符。数据类型为: key_t <sys/types.h>
4,有多种方法使 客户进程 和服务器进程 在同一IPC结构上汇聚。
1)服务器进程指定键IPC_PROVATE创建新IPC结构,并将标识符存放在某处,让客户进程取用。
2)在公共头文件中定义一个 两个进程 都认可的键。
3)两个进程 认可一个路径名和项目ID使用ftok函数将两个值变成一个KEY。
5,基本问题。
1)IPC结构在系统范围内起作用,没有引用计数。
2)IPC结构在文件系统中没有名字。
3)IPC不能使用文件描述符,所以不能对它们使用多路转接IO函数。
6,消息队列:消息的链接表。
7,信号量:一个计数器。用于为多个进程提供对共享数据对象的访问。
1)信号量通常在内核中实现。且减1操作为原子操作。
2)进程终止时,内核会自动检测信号量,并进行调整。
3)互斥量比信号量快很多。但如果可能尽量使用信号量。因为信号量的心痛支持度较高,而且复杂性更低。
8,共享存储:最快的IPC。
1)信号量可用于同步共享储存访问。
2)
15:POSIX 信号量。
1,相关函数。
1 创建信号量 / 使用现有信号量。 sem_t sem_open( const char *name, int oflag, mode_t mode, unsinged int value ); 2 释放信号量相关资源。 int sem_close( sem_t *sem ); 3 销毁一个命名信号量。 int sem_unlink( const char *name ); // 当最后一个引用关闭时 才销毁。 4 实现信号量减一操作。 int sem_trywait( sem_t *sem ); int sem_wait( sem_t *sem ); int sem_timedwait( sem_t *sem, const struct timespec *restrict tsptr ); // 1 资源为0时,发生阻塞。>0,减一 5 信号量+1 int sem_post( sem_t *sem ); 6 创建/销毁 一个未命名信号量。 int sem_init( sem_t *sem, int pshared, unsigned int value ); int sem_destroy( sem_t *sem ); 7 检索信号量值。 int sem_getvalue( sem_t *restrict sem, int *restrict valp ); // 1 除非使用额外的同步机制来避免竞争,否则此函数只能用于调试
2,相对XSI优点。
1)更高性能。
2)使用更简单:没有信号量集,部分操作统一化。
3)信号量删除表现的更完美:直到最后一次使用后才释放。
3,拥有两种性是:命名的和未命名的。差异:创建和销毁的形式上。
4,为增加移植性,信号量命名应该有一定规则:
1)第一个字符为 斜杠 / 。
2)名字不包含其他斜杠以避免实现定义的行为。
3)信号量名最大长度由实现定义。
5,P470 客户进程-服务器进程属性。
16:
17: