Linux IPC 机制
IPC(local procedure call):本地过程调用
RPC(remote procedure call):远程过程调用
一 消息传递
1 管道(无名管道)
#include <unistd.h>
int pipe(int pipefd[2]);
int pipe2(int pipefd[2], int flags);
flags:
O_CLOEXEC
O_DIRECT
O_NONBLOCK
只能用于有一个共同祖先进程的两个进程
fd[0] 是读,fd[1]是写,半双工
全双工由两个半双工管道组成
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
创建一个管道并启动另外一个进程
2 命名管道 FIFO
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
可用于两个无关进程
3 System V (XSI)
3.1 System V 消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
创建一个新的消息队列或者打开一个已存在的
int msgget(key_t key, int msgflg);
发送一个消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
读取一个消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
The msgp argument is a pointer to a caller-defined structure of the following general form:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
控制操作
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
IPC_RMID 从系统中删除指定消息队列
IPC_SET 按msqid_ds结构设置参数
The following members of the structure are updated: msg_qbytes,
msg_perm.uid, msg_perm.gid, and (the least significant 9 bits of) msg_perm.mode
IPC_STAT 给调用者返回当前msqid_ds结构
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages in queue */
msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
程序在使用的时候需要参考msgbuf,保留mtype,定义自己的数据结构
System V 的消息队列无法直接使用select或者poll,它不提供这种能力(poll操作)
System V 的消息队列能够读取指定优先级的消息,Posix消息队列没有这种能力。
新程序应该优先使用Posix消息队列。
3.2 System V 共享内存(共享存储)
#include <sys/ipc.h>
#include <sys/shm.h>
创建一个共享内存区,或者访问一个已存在的
int shmget(key_t key, size_t size, int shmflg);
#include <sys/types.h>
#include <sys/shm.h>
把共享内存附着到进程的地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
断开这个内存区
int shmdt(const void *shmaddr);
#include <sys/ipc.h>
#include <sys/shm.h>
操作共享内存区
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
IPC_RMID 从系统中删除shmid标识的共享内存区并释放
IPC_SET 设置shmid_ds结构体中的 shm_perm.ui shm_perm.gid shm_perm.mode
IPC_STAT 返回当前shmid_ds
4 POSIX
4.1 POSIX 消息队列
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <mqueue.h>
创建一个新的消息队列或打开一个已存在的消息队列
mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode,
struct mq_attr *attr);
Link with -lrt.
O_RDONLY 只能读消息
O_WRONLY 只能发送消息
O_RDWR 能读能写
O_CREAT 如果队列不存在则创建
O_EXCL 如果指定了O_CREAT并且队列存在, 则返回错误EEXIT
O_NONBLOCK 非阻塞模式
#include <mqueue.h>
关闭消息队列,进程终止时,它所打开的所有消息队列都会关闭
int mq_close(mqd_t mqdes);
#include <mqueue.h>
删除消息队列的name,消息队列仍存在
int mq_unlink(const char *name);
当一个消息队列的引用计数大于0时,其name就删除,但是该队列的拆除要到最后一个mq_close发生是才进行。
即使没有进程打开某个消息队列,该队列及其上的各个消息仍然存在,直到调用mq_unlink并且让它的引用计数达到0才删除。
#include <mqueue.h>
获得所有属性
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
设置其中某些属性
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr,
struct mq_attr *oldattr);
struct mq_attr {
long mq_flags; /* Flags: 0 or O_NONBLOCK */
long mq_maxmsg; /* Max. # of messages on queue */
long mq_msgsize; /* Max. message size (bytes) */
long mq_curmsgs; /* # of messages currently in queue */
};
#include <mqueue.h>
发送消息,参数:消息队列,消息内容,消息长度,优先级
int mq_send(mqd_t mqdes, const char *msg_ptr,
size_t msg_len, unsigned int msg_prio);
#include <time.h>
#include <mqueue.h>
当队列已满,并且没有O_NONBLOCK标记,会等待有限的时间abs_timeout
int mq_timedsend(mqd_t mqdes, const char *msg_ptr,
size_t msg_len, unsigned int msg_prio,
const struct timespec *abs_timeout);
#include <mqueue.h>
获取最旧且优先级最高的消息
ssize_t mq_receive(mqd_t mqdes, char *msg_ptr,
size_t msg_len, unsigned int *msg_prio);
#include <time.h>
#include <mqueue.h>
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
size_t msg_len, unsigned int *msg_prio,
const struct timespec *abs_timeout);
如果多个进程阻塞在mq_receive调用,当消息到来时,具有最高优先级和等待时间最长的进程将得到这条消息。
#include <mqueue.h>
给指定队列简历或删除异步事件通知,可以产生一个信号或者创建一个线程执行指定函数
int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
sevp.sigev_notify参数如下:
SIGEV_NONE 不通知
SIGEV_SIGNAL 发送一个指定信号给指定进程
SIGEV_THREAD 执行一个线程
4.2 POSIX 共享内存
Posix.1提供了两种共享内存区的方法1 内存映射问(memory-mapped file)和共享内存区对象(shared-memeory object)
这两者都需要调用mmap,通过open或者shm_open,然后调用mmap。
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
创建一个新的共享内存区对象或者打开一个已存在的共享内存区对象
int shm_open(const char *name, int oflag, mode_t mode);
删除一个共享内存区对象的名字
int shm_unlink(const char *name);
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
#include <unistd.h>
#include <sys/types.h>
指定共享内存区对象的大小
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
查看文件对象的信息
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
int fstatat(int dirfd, const char *pathname, struct stat *buf,
int flags);
5 信号
#include <signal.h>
typedef void (*sighandler_t)(int);
设置信号处理函数,若成功,返回以前的信号处理配置,出错返回SIG_ERR
sighandler_t signal(int signum, sighandler_t handler);
沿用的是BSD的信号策略
#include <sys/types.h>
#include <signal.h>
发送信号给进程或者进程组
int kill(pid_t pid, int sig);
pid>0 发送给ID为pid的进程
pid==0 发送给同组所有进程
pid<0 发送给进程组ID等于pid绝对值的所有进程
pid==-1 发送给有权限发送信号的所有进程,不包括系统进程集中的进程
#include <signal.h>
向进程自身发送信号
int raise(int sig);
raise(signo) 等价于 kill(getpid(), signo)
#include <unistd.h>
设置一个定时器,产生SIGALRM信号
unsigned int alarm(unsigned int seconds);
#include <unistd.h>
使进程挂起,直到捕捉到信号
int pause(void);
#include <signal.h>
告诉内核不允许发生信号集中的信号
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
等等
二 用户空间同步(锁)
1 互斥锁和条件变量
互斥锁
#include <pthread.h>
给一个互斥锁上锁,阻塞
int pthread_mutex_lock(pthread_mutex_t *mutex);
上锁,非阻塞,如果互斥锁已锁,则返回EBUSY
int pthread_mutex_trylock(pthread_mutex_t *mutex);
解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
互斥锁保护的是临界区中被操纵的数据
条件变量
#include <pthread.h>
等待条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
#include <pthread.h>
发送信号唤醒正在等待的一个线程
int pthread_cond_signal(pthread_cond_t *cond);
唤醒正在等待条件变量的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
互斥锁用于上锁,条件变量用于等待。
每个条件变量都必须与一个互斥锁关联。
2 读写锁
读写锁的分配规则如下:
- 若没有线程给某个读写锁用于写,那么任意数目的线程可以持有读写锁用于读
- 仅当没有线程持有某个读写锁用于读或者写时,才能分配该读写锁用于写
适合读多写少的情况
对给定资源的共享访问称为共享-独占(shared-exclusive)上锁
获取一个读写锁用于读称为共享锁(shared lock)
获取一个读写锁用于写称为独占锁(exclusive lock)
#include <pthread.h>
摧毁锁,不需要该读写锁的时候
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
初始化锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
获取读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
尝试读,不能获得立刻返回
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
写
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
尝试写
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
释放锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
线程取消
当一个线程被其他线程调用 int pthread_cancel(pthread_t thread) 取消。
线程需要提前设置 安装和删除清理处理程序。
#include <pthread.h>
设置线程被取消时所调用的函数地址
void pthread_cleanup_push(void (*routine)(void *), void *arg);
当execute值不为0时,总是调用清理函数。
void pthread_cleanup_pop(int execute);
3 记录上锁
3.1 记录上锁和文件上锁
记录上锁(record locking)术语用来描述共享某个文件的读写操作锁。
应用需指定带上锁或解锁部分的字节范围。
3.2 Posix 记录上锁
对于Posix记录上锁来说,粒度就是单个字节。粒度越小,运允许同时操作的用户越多。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
第三个参数指向一个结构体:
struct flock {
...
short l_type; /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start: 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; /* PID of process blocking our lock (set by F_GETLK and F_OFD_GETLK) */
...
};
flock结构描述锁的类型(读出锁或写入锁)以及待锁住的字节范围。
第二个参数有三个命令:
F_SETLK 获取arg指向的flock结构描述的锁
F_SETLKW 与上个命令类似,不同的是会阻塞到该锁能被授予该进程
F_GETLK 获取锁的信息,是否已存在,对应的pid等信息
3.3劝告性锁
Posix记录上锁是一种劝告性锁(advisory locking)
内核维护着各个进程上锁的文件信息,但是不能防止一个进程无视劝告性锁而写一个读锁定文件等。
劝告性上锁对于协作进程足够了。
有些系统可能提供强制性上锁
3.4 fcntl() flock() lockf()
#include <sys/file.h>
int flock(int fd, int operation);
#include <unistd.h>
int lockf(int fd, int cmd, off_t len);
fcntl()、lockf()、flock() 都是给文件加锁的。
flock和fcntl是系统调用,而lockf是封装fcntl()的库函数。
flock只能给整个文件加锁
Linux 中 fcntl()、lockf、flock 的区别
http://blog.jobbole.com/104331/
4 POSIX 信号量(信号灯)
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
#include <semaphore.h>
关闭信号量,不删除
int sem_close(sem_t *sem);
#include <semaphore.h>
断开信号量,当引用数等于0时,就会删除
int sem_unlink(const char *name);
#include <semaphore.h>
如果信号量的值大于0,则减1,然后返回。
int sem_wait(sem_t *sem);
如果信号量的值等于0,则睡眠等待值大于0,再减1返回
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
#include <semaphore.h>
把信号量的值加1
int sem_post(sem_t *sem);
#include <semaphore.h>
获得信号量当前的值
int sem_getvalue(sem_t *sem, int *sval);
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
Posix信号灯可以是有名的,也可以是基于内存的。
5 System V 信号量(信号灯)
System V信号灯集:一个或者多个信号灯构成一个集合,一般数量在25个左右
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
创建一个信号灯集或者访问一个已存在的信号灯集
int semget(key_t key, int nsems, int semflg);
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
对其中的一个或者多个信号灯操作
int semop(int semid, struct sembuf *sops, size_t nsops);
int semtimedop(int semid, struct sembuf *sops, size_t nsops,
const struct timespec *timeout);
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
对一个信号灯执行控制操作
int semctl(int semid, int semnum, int cmd, ...);
IPC_STAT 返回当前semid_ds结构
IPC_SET 设置semid_ds结构中的三个成员:sem_perm.uid sem_perm.gid sem_perm.mode
IPC_RMID 从系统中删除semid指定的信号灯集
GETALL 获得指定信号灯集的每个成员的semval值
GETNCNT
GETPID
GETVAL 获得当前semval值
GETZCNT 获得当前semzcnt值
SETALL 设置指定信号灯集的每个成员的semval值
SETVAL
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};
Linux 文件系统
Linux内核为了兼容不同的文件系统,引入了中间层,虚拟文件系统(Virtual File System, 简称 VFS)。
VFS提供一组标准的文件操作接口给用户空间的程序,如:read()、write()、lseek()等,用户程序无需关心所操作的文件属于哪个文件系统,以及如何实现的。
VFS有4个主要对象:
超级快对象:存储一个已安装的文件系统的控制信息 super_block
索引节点对象:描述一个文件 inode
目录项对象:描述一个目录项,是路径的组成部分 dentry
文件对象:描述由进程打开的文件 file
inode代表的是物理意义上的文件,记录物理上的属性,在磁盘上有对应的印像。
inode仅仅只是保存了文件对象的属性信息,包括:权限、属组、数据块的位置、时间戳等信息。但是并没有包含文件名,文件在文件系统的目录树中所处的位置信息。
dentry结构代表的是逻辑意义上的文件,在磁盘上没有对应的映像。引入目录项的概念主要是出于方便查找文件的目的。
dentry在内核中起到了连接不同的文件对象inode的作用,进而起到了维护文件系统目录树的作用。
dentry是一个纯粹的内存结构,由文件系统在提供文件访问的过程中在内存中直接建立。dentry中包含了文件名,文件的inode号等信息。
file文件对象是已打开的文件在内存中的表示,主要用于建立进程和磁盘上的文件的对应关系。
软连接有自己的inode,有自己的内容。
硬链接只是将自己的名字写在上级目录的内容(文件名与inode no.的映射表)中。而其inode号即是目标文件的inode。这样硬连接与目标文件一起共用一个inode,使用引用计数来管理硬连接。
task_struct中的一个域files_struct
文件描述符是进程打开的文件对象数组的索引值
文件对象通过域f_dentry找到它对应的dentry对象,再由dentry对象的域d_inode找 到它对应的索引结点
文件对象所对应的文件操作函数列表是通过索引结点的域i_fop得到的
i_fop存储的是struct file_operation
从文件 I/O 看 Linux 的虚拟文件系统
https://www.ibm.com/developerworks/cn/linux/l-cn-vfs/
文件读写:
用户空间调用read()
内核系统调用sys_read()
file=fget(fd)获取文件对应file结构体的地址
安全检查file->f_mode是否允许访问等
调用挂载的file->f_op->read或者file->f_op->write
对于EXT2文件系统,挂载的是generic_file_read() 或 generic_file_write() 通用访问函数
调用后来到I/O调度等请求传输
问题1:为什么终端关闭,终端里运行的进程(非nobup、tmux、screen等)会被终止
【整理】SIGHUP问题梳理
https://my.oschina.net/moooofly/blog/489521
- 内核驱动发现终端(或伪终端)关闭,给终端对应的控制进程(bash)发 SIGHUP
- bash 收到 SIGHUP 后,会给各个作业(包括前后台)发送 SIGHUP,然后自己退出
- 前后台的各个作业收到来自 bash 的 SIGHUP 后退出(如果程序会处理 SIGHUP,就不会退出)
问题2:kill以及kill -9发送的是什么信号
SIGTERM(15) 和 SIGKILL(9)
- Signal Value Action Comment
- <span style="text-shadow: transparent 0px 0px 0px, rgb(0, 0, 0) 0px 0px 0px !important; margin: 0px; padding: 0px; border: none; color: black; background-color: inherit;"--<-----------------------------------------------------------------------
- SIGHUP 1 Term Hangup detected on controlling terminal
- or death of controlling process
- SIGINT 2 Term Interrupt from keyboard
- SIGQUIT 3 Core Quit from keyboard
- SIGILL 4 Core Illegal Instruction
- SIGABRT 6 Core Abort signal from abort(3)
- SIGFPE 8 Core Floating point exception
- SIGKILL 9 Term Kill signal
- SIGSEGV 11 Core Invalid memory reference
- SIGPIPE 13 Term Broken pipe: write to pipe with no readers
- SIGALRM 14 Term Timer signal from alarm(2)
- SIGTERM 15 Term Termination signal
- SIGUSR1 30,10,16 Term User-defined signal 1
- SIGUSR2 31,12,17 Term User-defined signal 2
- SIGCHLD 20,17,18 Ign Child stopped or terminated
- SIGCONT 19,18,25 Continue if stopped
- SIGSTOP 17,19,23 Stop Stop process
- SIGTSTP 18,20,24 Stop Stop typed at tty
- SIGTTIN 21,21,26 Stop tty input for background process
- SIGTTOU 22,22,27 Stop tty output for background process
linux 信号处理 二 (信号的默认处理)
http://www.cnblogs.com/jiangzhaowei/p/4113644.html
信号 默认处理 场景
SIGHUP 1 终止收到该信号的进程 终端结束,终端里的每个作业会收到
SIGINT 2 终止收到该信号的进程 向终端输入终端键(Control+C或DELETE)时
SIGKILL 9 终止收到该信号的进程 强行杀死进程
SIGPIPE 13 终止收到该信号的进程 向已断开的socket写数据时,会收到
SIGTERM 15 终止收到该信号的进程 kill pid
SIGKILL(9)与SIGSTOP(19)是不能捕获的