文章目录
内核同步
内核为什么要同步
自旋锁 spinlock
- 常用于中断处理程序。
- 自旋锁一定是由系统内核调用的。不可能在用户程序中由用户请求自旋锁。当一个用户进程拥有自旋锁期间,内核是把代码提升到管态的级别上运行。在内部,内核能获取自旋锁,但任何用户都做不到这一点。
Linux自旋锁是不可递归的。因此,如果你试图得到一个你正持有的锁,你必须自旋等你自己释放这个锁。但是你处于自旋忙等中,所以你永远没有机会释放锁,于是你被自己锁死了。
#include<linux/spinlock.h>
DEFINE_SPINLOCK(mrlock);
spin_lock(&mr_lock);
/*临界区*/
spin_unlock(&mr_lock);
内核也提供了中断处理程序请求锁时关闭中断的接口。
信号量
struct semaphore name;
sema_init(&name,count);//以指定的计数值初始化动态创建的信号量
down_interruptible(struct semaphore *);//试图获取信号量,若已被争用进入可中断睡眠状态
up(struct semaphore *);//释放指定信号量,若睡眠队列不为空唤醒其中一个任务
注意:
- 只有在进程上下文中才能获取信号量锁,因为中断上下文是不能进行调度的。
进程同步
管道
什么是管道?
shell中 | 连接两个命令,shell会将前后两个进程的输入输出用一个管道相连,以便达到进程间通信的目的。管道本质就是一个文件。前面的进程以写方式打开文件,后面进程以读方式打开。无名管道存在高速缓冲。
管道类型
- 匿名管道:匿名管道最常见的形态就是我们在shell操作中最常用的”|”。它的特点是只能在具有公共祖先的进程间通信,即或是父子关系进程间、或是在兄弟关系进程间通信。父进程在产生子进程前必须打开一个管道文件,(每次执行shell命令就会fork一个子进程,每次遇到 | 回再fork一个子进程)然后fork产生子进程,这样子进程通过拷贝父进程的进程地址空间获得同一个管道文件的描述符,以达到使用同一个管道通信的目的。此时除了父子进程外,没人知道这个管道文件的描述符,所以通过这个管道中的信息无法传递给其他进程。这保证了传输数据的安全性,当然也降低了管道了通用性,于是系统还提供了命名管道。
兄弟进程间怎么使用匿名管道通信:兄弟都继承父亲的描述符,所以可以通信。 - 通过mkfifo命令创建一个命名管道,这跟创建一个文件没有什么区别,文件类型是p类型。有了这个管道文件,系统中就有了对这一个管道的全局名称,任何两个不相关的进程都可以通过这个管道进行通信了。
root@xxx-virtual-machine:~/test# mkfifo pipe
root@xxx-virtual-machine:~/test# ls -l
总用量 0
prw-r--r-- 1 root root 0 12月 24 23:17 pipe
让一个进程向这个管道文件写东西:发现会阻塞在这里。
打开另一个终端开启一个进程对这个管道进行读操作:上面才会从阻塞状态返回
管道使用例子
- 匿名管道:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#define STRING "hello world!"
int main(){
int pipefd[2];
pid_t pid;
char buf[BUFSIZ];
if (pipe(pipefd) == -1) {perror("pipe()");exit(1);}
pid = fork();
if (pid == -1) {perror("fork()");exit(1);}
if (pid == 0) {
/* this is child. */
close(pipefd[1]);
printf("Child pid is: %d\n", getpid());
if (read(pipefd[0], buf, BUFSIZ) < 0) {perror("write()");exit(1);}
printf("%s\n", buf);
} else {
/* this is parent */
close(pipefd[0]);
printf("Parent pid is: %d\n", getpid());
snprintf(buf, BUFSIZ, "Message from parent: My pid is: %d", getpid());
if (write(pipefd[1], buf, strlen(buf)) < 0) {perror("write()");exit(1);}
wait(NULL);
}
}
- 命名管道:
创建一个命名管道:使用第一个参数作为创建的文件路径。创建完之后,其他进程就可以使用open()、read()、write()标准文件操作等方法进行使用了。进程对有名管道的访问方式与访问其他普通文件一样,都需要先用open()系统调用打开它。
if (mknod(argv[1], 0600|S_IFIFO, 0) < 0) {
perror("mknod()");
exit(1);
}
为什么使用管道 不使用文件通信:
- 有名管道:在文件系统中存在一个文件标识(文件名),但是管道文件不占据磁盘空间,需要传递的数据缓存在内存区域。可用于运行与通一系统中的任意两个进程。(有属性信息,inode节点存在磁盘上)
- 无名管道:用于父进程和子进程间的通信。无属性信息,没有inode节点了。
管道文件,半双工通讯:数据流向是单向的。使用半双工管道时,管道的两端都可能有多个进程进行读写处理。如果再加上线程,则事情可能变得更复杂。实际上,我们在使用管道的时候,并不推荐这样来用。管道推荐的使用方法是其单工模式:即只有两个进程通信,一个进程只写管道,另一个进程只读管道。
信号量
简单过程:
semaphore sv = 1;
loop forever {
P(sv);
critical code section;
V(sv);
noncritical code section;
}
头文件以及函数:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int semctl(int sem_id, int sem_num, int command, ...);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
共享内存
什么是共享内存
共享内存可以被两个或以上的进程映射到自身的地址空间中,这块内存的页面在每一个共享它的进程的页表中都有页表项引用。访问共享内存区域和访问进程独有的内存区域一样快,并不需要通过系统调用或者其它需要切入内核的过程来完成。同时它也避免了对数据的各种不必要的复制。
因为系统内核没有对访问共享内存进行同步,您必须提供自己的同步措施。例如,在数据被写入之前不允许进程从共享内存中读取信息、不允许两个进程同时向同一个共享内存地址写入数据等。解决这些问题的常用方法是通过使用信号量进行同步。
系统对每个共享内存区设置的数据结构
使用共享内存的过程
- int shamget(key_t key,int size,int shmflg);
功能:创建一块共享内存,若已存在,则返回其标识符。创建不成功返回-1
- void *shmat(int shmid,const void *shmaddr,int shmflg);
1)功能:把指定共享内存区域映射到进程虚拟地址空间,成功返回映射的起始地址,并对shmid_kernel结构中共享计数shm_nattch加1,否则返回-1.
2)参数:
shmid: 共享内存的标识符。
shmaddr: 指定共享内存映射到进程虚拟地址空间的位置,若置为NULL或0,则让系统确定一个合适的位置。
shmflg: 进程对共享内存的读写属性,SHM_RDONLY为只读模式,其他为读写模式。 - int shmdt(const void *shmaddr);
对 exit 或任何exec族函数的调用都会自动使进程脱离共享内存块。
功能:断开共享内存在调用进程中的影射,禁止本进程访问此内存。若成功,则会对shmid_kernel结构中的共享计数shm_nattch减1,当shm_nattch为0,若成功返回0,否则返回-1.
参数:shmaddr表示欲断开映射的共享内存的起始地址。 - int shmctl(int shmid, int cmd, struct shmid_ds *buf)
功能:获取或设置共享内存的属性信息或者销毁( ?)
消息队列
IPC消息队列相关的系统调用:
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数:
int msgget(key_t key, int msgflg);
- key:消息队列的键值;
- msgflg:对消息队列的访问权限和控制命令组合。
权限:分别表示属主、同组用户及其他用户的权限。
控制命令:IPC_CREATE key对于的消息队列存不存在则创建,存在返回标识符;IPC_EXCL|IPC_CREATE 存在创建,已存在返回-1.
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
- msgid:消息队列的标识符
- msgp:存放欲发送消息内容的消息缓冲区的指针。
- msgsz: 消息正文的长度
- msgflg:发送标志
0:消息队列满时,调用进程(发送进程)将会阻塞,知道消息队列可以写入该消息。
IPC_NOWAIT:消息队列满时,调用进程立即返回-1。
MSG_noerror:消息正文长度超过msg_sz时,不报错,而是直接截取多余部分。
ssize_t msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
- msgid:消息队列的标识符
- msgp:存放接受消息内容的消息缓冲区的指针。
- msgsz: 消息正文的长度
- msgtyp:接受消息的类型
等于0:接受消息队列中第一个消息;
大于0:接受第一个类型为msgtyp的消息
小于0:接受第一个类型<=msgtyp的消息 - msgflg:接收消息的标志
0:没有可以接受,阻塞
IPC_NOWAIT:没有可以接收,返回-1
MSG_EXCEPT:返回第一个不是mygtyp的消息。
MSG_noerror:消息正文长度超过msg_sz时,不报错,而是直接截取多余部分。
- int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- cmd:要在消息队列上执行的命令,TPC_RMD:删除消息队列。
- buf:用户空间的一个缓存,接受或提供状态信息。
线程同步
信号量
#include <semaphore.h>
sem_t bin_sem;
res = sem_init(&bin_sem, 0, 0);
sem_wait(&bin_sem);
sem_post(&bin_sem);
sem_destroy(&bin_sem);
mutex互斥
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex));
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);