操作系统基础知识---进阶篇
互斥量(互斥锁):互斥量是最简单的线程同步的方法,处于两态之一的变量:解锁和加锁,两个状态可以保证资源访问的串行。
原子性:原子性指一系列操作不可被中断的特性,这一系列操作要么全部执行,要么全部没有执行,不存在部分执行部分未执行的情况。
//互斥量示例 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <vector> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 临界资源 int num = 0; // 生产者 void *producer(void*){ int times = 100000000; while(times --){ pthread_mutex_lock(&mutex); num += 1; pthread_mutex_unlock(&mutex); } } // 消费者 void *comsumer(void*){ int times = 100000000; while(times --){ pthread_mutex_lock(&mutex); num -= 1; pthread_mutex_unlock(&mutex); } } int main(){ printf("Start in main function."); pthread_t thread1, thread2; pthread_create(&thread1, NULL, &producer, NULL); pthread_create(&thread2, NULL, &comsumer, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("Print in main function: num = %d\n", num); return 0; }
自旋锁:自旋锁也是一种多线程同步的变量,使用自旋锁的线程会反复检查锁变量是否可用,自旋锁不会让出CPU,是一种忙等待状态(死循环等待锁被释放)。
相比于互斥量,自旋锁避免了进程或线程的上下文切换的开销,操作系统内部很多地方使用的都是自旋锁,但自旋锁不适合在单核CPU使用。
//自旋锁示例 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <vector> pthread_spinlock_t spin_lock; int num = 0; void *producer(void*){ int times = 10000000; while(times --){ pthread_spin_lock(&spin_lock); num += 1; pthread_spin_unlock(&spin_lock); } } void *comsumer(void*){ int times = 10000000; while(times --){ pthread_spin_lock(&spin_lock); num -= 1; pthread_spin_unlock(&spin_lock); } } int main(){ printf("Start in main function.\n"); pthread_spin_init(&spin_lock, 0); pthread_t thread1, thread2; pthread_create(&thread1, NULL, &producer, NULL); pthread_create(&thread2, NULL, &comsumer, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("Print in main function: num = %d\n", num); return 0; }
读写锁是一种特殊的自旋锁,它允许多个读者同时访问资源以提高读性能,对于写操作则是互斥的。
//读写锁示例 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <vector> int num = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; void *reader(void*){ int times = 10000000; while(times --){ // pthread_rwlock_rdlock(&rwlock); pthread_mutex_lock(&mutex); if (times % 1000 == 0){ // printf("print num in reader: num = %d\n", num); // sleep(1); usleep(10); } pthread_mutex_unlock(&mutex); // pthread_rwlock_unlock(&rwlock); } } void *writer(void*){ int times = 10000000; while(times --){ pthread_mutex_lock(&mutex); // pthread_rwlock_wrlock(&rwlock); num += 1; // pthread_rwlock_unlock(&rwlock); pthread_mutex_unlock(&mutex); } } int main(){ printf("Start in main function.\n"); pthread_t thread1, thread2, thread3; pthread_create(&thread1, NULL, &reader, NULL); pthread_create(&thread2, NULL, &reader, NULL); pthread_create(&thread3, NULL, &writer, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_join(thread3, NULL); printf("Print in main function: num = %d\n", num); return 0; }
条件变量是一种相对复杂的线程同步方法,条件变量允许线程睡眠,直到满足某种条件,当满足条件时,可以向该线程发送信号,通知唤醒。
之前的生产者消费者模型有两个条件需要进行约束:
①当缓冲区小于等于0时,不允许消费者消费,消费者必须等待;
②缓冲区满时,不允许生产者往缓冲区生产,生产者必须等待;
当生产者生产一个产品时,唤醒可能等待的消费者,当消费者消费一个产品时,唤醒可能等待的生产者。
//条件变量示例 #include <iostream> #include <stdio.h> #include <stdlib.h> #include <vector> #include <queue> #include <unistd.h> #include <pthread.h> int MAX_BUF = 100; int num = 0; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void* producer(void*){ while(true){ pthread_mutex_lock(&mutex); while (num >= MAX_BUF){ // 等待 printf("缓冲区满了, 等待消费者消费...\n"); pthread_cond_wait(&cond, &mutex); } num += 1; printf("生产一个产品,当前产品数量为:%d\n", num); sleep(1); pthread_cond_signal(&cond); printf("通知消费者...\n"); pthread_mutex_unlock(&mutex); sleep(1); } } void* consumer(void*){ while(true){ pthread_mutex_lock(&mutex); while (num <= 0){ // 等待 printf("缓冲区空了, 等待生产者生产...\n"); pthread_cond_wait(&cond, &mutex); } num -= 1; printf("消费一个产品,当前产品数量为:%d\n", num); sleep(1); pthread_cond_signal(&cond); printf("通知生产者...\n"); pthread_mutex_unlock(&mutex); } } int main(){ pthread_t thread1, thread2; pthread_create(&thread1, NULL, &consumer, NULL); pthread_create(&thread2, NULL, &producer, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); return 0; }
①fork系统调用是用于创建进程的;
②fork创建的进程初始化与父进程一样;
③系统会为fork的进程分配新的资源;
④fork系统调用无参数;
⑤fork会返回两次,分别返回子进程id和0
⑥返回子进程id的是父进程,但会0的是子进程
#include <iostream> #include <cstring> #include <stdio.h> #include <unistd.h> using namespace std; int main() { pid_t pid; int num = 888; pid = fork(); if(pid == 0){ cout << "这是一个子进程." << endl; cout << "num in son process: " << num << endl; while(true){ num += 1; cout << "num in son process: " << num << endl; sleep(1); } } else if(pid > 0){ cout << "这是一个父进程." << endl; cout << "子进程id: " << pid << endl; cout << "num in father process: " << num << endl; while(true){ num -= 1; cout << "num in father process: " << num << endl; sleep(1); } } else if (pid < 0){ cout << "创建进程失败." << endl; } return 0; }
在某种程度上,多进程是共同使用物理内存的;由于操作系统的进程管理,进程空间的内存空间是独立的;进程默认是不能访问进程空间之外的内存空间的。
共享存储允许不相关的进程访问同一片物理内存;
共享内存是两个进程之间共享和传递数据最快的方式;
共享内存未提供同步机制,需要借助其他机制管理访问。
//共享内存示例 //common.h #ifndef __COMMON_H__ #define __COMMON_H__ #define TEXT_LEN 2048 // 共享内存的数据结构 struct ShmEntry{ // 是否可以读取共享内存,用于进程间同步 bool can_read; // 共享内存信息 char msg[2048]; }; #endif //server.cpp #include "common.h" #include <sys/shm.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <iostream> int main() { // 共享内存的结构体 struct ShmEntry *entry; // 1. 申请共享内存 int shmid = shmget((key_t)1111, sizeof(struct ShmEntry), 0666|IPC_CREAT); if (shmid == -1){ std::cout << "Create share memory error!" << std::endl; return -1; } // 2. 连接到当前进程空间/使用共享内存 entry = (ShmEntry*)shmat(shmid, 0, 0); entry->can_read = 0; while (true){ if (entry->can_read == 1){ std::cout << "Received message: " << entry->msg << std::endl; entry->can_read = 0; }else{ std::cout << "Entry can not read. Sleep 1s." << std::endl; sleep(1); } } // 3. 脱离进程空间 shmdt(entry); // 4. 删除共享内存 shmctl(shmid, IPC_RMID, 0); return 0; } //client.cpp #include "common.h" #include <sys/shm.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <iostream> int main() { struct ShmEntry *entry; // 1. 申请共享内存 int shmid = shmget((key_t)1111, sizeof(struct ShmEntry), 0666|IPC_CREAT); if (shmid == -1){ std::cout << "Create share memory error!" << std::endl; return -1; } // 2. 连接到当前进程空间/使用共享内存 entry = (ShmEntry*)shmat(shmid, 0, 0); entry->can_read = 0; char buffer[TEXT_LEN]; while (true){ if (entry->can_read == 0){ std::cout << "Input message>>> "; fgets(buffer, TEXT_LEN, stdin); strncpy(entry->msg, buffer, TEXT_LEN); std::cout << "Send message: " << entry->msg << std::endl; entry->can_read = 1; } } // 3. 脱离进程空间 shmdt(entry); // 4. 删除共享内存 shmctl(shmid, IPC_RMID, 0); return 0; }
域套接字是一种高级的进程间通信的方法;
Unix域套接字可以用于同一机器进程间通信。
套接字(socket)原是网络通信中使用的术语;
Unix系统提供的域套接字提供了网络套接字类似的功能。
//套接字示例 //server.cpp #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <strings.h> #include <string.h> #include <netinet/in.h> #include <stdlib.h> #include <unistd.h> #include <iostream> // 域套接字 #define SOCKET_PATH "./domainsocket" #define MSG_SIZE 2048 int main() { int socket_fd, accept_fd; int ret = 0; socklen_t addr_len; char msg[MSG_SIZE]; struct sockaddr_un server_addr; // 1. 创建域套接字 socket_fd = socket(PF_UNIX,SOCK_STREAM,0); if(-1 == socket_fd){ std::cout << "Socket create failed!" << std::endl; return -1; } // 移除已有域套接字路径 remove(SOCKET_PATH); // 内存区域置0 bzero(&server_addr,sizeof(server_addr)); server_addr.sun_family = PF_UNIX; strcpy(server_addr.sun_path, SOCKET_PATH); // 2. 绑定域套接字 std::cout << "Binding socket..." << std::endl; ret = bind(socket_fd,(sockaddr *)&server_addr,sizeof(server_addr)); if(0 > ret){ std::cout << "Bind socket failed." << std::endl; return -1; } // 3. 监听套接字 std::cout << "Listening socket..." << std::endl; ret = listen(socket_fd, 10); if(-1 == ret){ std::cout << "Listen failed" << std::endl; return -1; } std::cout << "Waiting for new requests." << std::endl; accept_fd = accept(socket_fd, NULL, NULL); bzero(msg,MSG_SIZE); while(true){ // 4. 接收&处理信息 recv(accept_fd, msg, MSG_SIZE, 0); std::cout << "Received message from remote: " << msg <<std::endl; } close(accept_fd); close(socket_fd); return 0; } //client.cpp #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <strings.h> #include <string.h> #include <netinet/in.h> #include <stdlib.h> #include <unistd.h> #include <iostream> #define SOCKET_PATH "./domainsocket" #define MSG_SIZE 2048 int main() { int socket_fd; int ret = 0; char msg[MSG_SIZE]; struct sockaddr_un server_addr; // 1. 创建域套接字 socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); if(-1 == socket_fd){ std::cout << "Socket create failed!" << std::endl; return -1; } // 内存区域置0 bzero(&server_addr,sizeof(server_addr)); server_addr.sun_family = PF_UNIX; strcpy(server_addr.sun_path, SOCKET_PATH); // 2. 连接域套接字 ret = connect(socket_fd, (sockaddr *)&server_addr, sizeof(server_addr)); if(-1 == ret){ std::cout << "Connect socket failed" << std::endl; return -1; } while(true){ std::cout << "Input message>>> "; fgets(msg, MSG_SIZE, stdin); // 3. 发送信息 ret = send(socket_fd, msg, MSG_SIZE, 0); } close(socket_fd); return 0; }
域套接字只提供了单机简单的可靠的进程通信同步服务,只能在单机使用,不同跨机器使用。
做一枚奔跑的老少年!