多进程编程

fork

#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);

该函数一次调用,两次返回。在父进程中返回子进程的PID,在子进程中返回0,失败返回-1.
fork函数复制当前进程,在内核进程表中创建一个新的进程表项,堆指针、栈指针、标志寄存器的值与原进程相同。但是子进程的PPID被设置为原进程的PID,信号位图被清除(原进程的信号处理函数不对新进程起作用)。
子进程的代码与父进程完全相同,同时复制父进程的数据(堆数据、栈数据、静态变量等)。
复制采用的写时复制机制,即只有任一进程对数据执行了写操作时,才会真正进行复制操作(先产生缺页中断,然后操作系统给子进程分配内存并复制父进程的数据)。
文件描述符: 子进程也会继承父进程打开的文件描述符,因此文件描述符的引用计数会+1. 另外,父进程的用户根目录、当前工作目录等变量的引用也会加一。

exec

在子进程中执行其他程序,即替换当前进程映像,使用exec系列函数:
在这里插入图片描述
如果函数正确执行,原程序中exec调用之后的代码都不会执行,因为原程序已经被exec指定的程序完全替代(包括代码和数据)。

处理僵尸进程

僵尸进程

  • 当子进程结束运行时,内核不会立即释放该进程的进程表表项(pid,退出时间,退出状态)。以满足父进程后续对该子进程退出状态的查询,在子进程结束运行之后,父进程读取其退出状态之前,我们称该进程在僵尸态

如果父进程没有正确处理子进程的返回信息,那么子进程将停留在僵尸态,占据内核资源,过多的僵尸进程可能导致新的进程无法创建,所以应该正确处理子进程的结束过程。

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid, int *stat_loc, int options);

wait函数将阻塞,直到该进程的某个子进程结束,他返回子进程的PID,并将子进程的退出状态信息存储于stat_loc指向的内存中,可以使用宏来解释退出状态。
在这里插入图片描述
waitpid只等待由pid参数指定的子进程,如果pid赋值-1,那么于wait相同,即等待任意子进程。stat_loc含义相同。options是可选的,设置为WNOHANG则waitpid调用是非阻塞的:如果子进程还未结束,则返回0,如果子进程正常退出了,返回PID,失败时返回-1.

为了提高程序效率,我们一般在子进程退出后才调用waitpid。当子进程结束后,会给父进程发送一个信号SIGCHLD,我们可以在父进程捕获这个信号,并处理子进程。

static void handle_child(int sig){
	pid_t pid;
	int stat;
	// 设置为-1,监听所有子进程
	while((pid = waitpid(-1, &stat, WNOHANG)) > 0){
		// handle pid的子进程
	}
}

管道 pipe

管道可以在父子进程间传递消息,因为fork()调用之后两个文件描述符fd[0], fd[1]都保持打开。一对这样的文件描述符只能保证单方向的文件传输,父子进程必须有一个关闭fd[0],另一个关闭fd[1]。如果需要双向通信,则需要使用两个管道,或者使用socketpair

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<string.h>
using namespace std;
int main() {
	int fd[2];
	int* readfd = &fd[0], * writefd = &fd[1];
	const char* s = "Hello, this is a test text";
	char readbuf[100];
	memset(readbuf, '\0', 100);
	int re;
	re = pipe(fd);
	if (re == -1) {
		perror("pipe");
		return -1;
	}
	pid_t pid = fork();
	if (pid == -1) {
		perror("fork");
		return -1;
	}

	if (pid != 0) {
		// parent
		close(*readfd);
		write(*writefd, s, strlen(s));
	}
	else {
		close(*writefd);
		int len = read(*readfd, readbuf, sizeof(readbuf));
		printf("Have read %d len data, is %s", len, readbuf);
	}
	return 0;
}

在这里插入图片描述

信号量

#include<sys/wait.h>
#include<sys/sem.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

union semun
{
    int val;
    semid_ds* buf;
    unsigned short int* array;
    seminfo* __buf;
};

void pv(int sem_id, int op){
    sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = op;
    sem_b.sem_flg = SEM_UNDO;

    semop(sem_id, &sem_b, 1);
}

int main(){
    int sem_id = semget(IPC_PRIVATE, 1, 0666);
    semun sem_un;
    sem_un.val = 1;
    semctl(sem_id, 0, SETVAL, sem_un);

    int pid = fork();

    if(pid < 0){
        return -1;
    }else if(pid == 0){
        printf("In child , try to get binary sem\n");
        pv(sem_id, -1);
        printf("Child got the sem and will hold it 5 seconds\n");
        sleep(5);
        pv(sem_id, 1);
        exit(0);
    }else{
        sleep(1);
        printf("In Parent , try to get binary sem\n");
        pv(sem_id, -1);
        printf("Parent got the sem and will hold it 5 seconds\n");
        sleep(5);
        pv(sem_id, 1);
    }

    waitpid(pid, NULL, 0);
    semctl(sem_id, 0, IPC_RMID, sem_un);
    return 0;
}

信号量由一组系统调用实现

#include<sys/sem.h>
/* Get semaphore.  */
int semget (key_t __key, int __nsems, int __semflg);

/* Control semaphore. */
int semctl (int __semid, int __semnum, int __cmd, ...);

/* Operate on semaphore.  */
int semop (int __semid, struct sembuf *__sops, size_t __nsops);

semget

int semget (key_t key, int num_sems, int sem_flags);
semget系统调用创建一个新的信号量集,或者获取一个已经存在的信号量集
其中key标识全局唯一的信号量集,num_sems指定要创建/获取的信号量集中信号量的数目,如果是创建信号量集,必须显式指定。sem_flags指定一组标志。其格式与含义与openmode参数相似。

创建信号量集后,内核会创建与之关联的数据结构体semid_ds并初始化。
在这里插入图片描述

semctl

可以用来控制信号量集
int semctl (int sem_id, int sem_num, int cmd, ...);
sem_id指定信号量集,sem_num指定要控制的信号量在信号量集中的编号(0 —— n), cmd时信号,后面的是根据cmd的可选项,推荐格式如下:

union semun{
	int val; // 用于SETVAL命令
	struct semid_ds* buf; // IPC_STAT和IPC_SET
	unsigned short* array; // GETALL和SETALL
	seminfo* __buf; // IPC_INFO
};

struct seminfo{
  int semmap; // not use
  int semmni; // 系统最多可拥有的信号量集数目
  int semmns; // 系统最多可拥有的信号量数目
  int semmnu; // not use
  int semmsl; // 一个信号量集最多可包含的信号量数目
  int semopm; // semop一次最多可执行多少个sem_op操作数目
  int semume; // not use
  int semusz; // sem_undo结构体大小
  int semvmx; // 最大允许的信号量值
  int semaem; // 最多允许的undo次数
};

在这里插入图片描述

semop

int semop (int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

struct sembuf{
	unsigned short int sem_num; // 信号量集中待操作的信号量编号
	short int sem_op;
	short int sem_flg;
};

对信号量集上的信号量进行操作。
声明一个sembuf数组,然后依次设定对其的操作字段,设定数组长度num_sem_ops,然后系统会依次执行。
在这里插入图片描述
在这里插入图片描述

共享内存

共享内存是最高效的IPC机制,因为它不涉及进程间的任何数据传输。但是我们必须使用其他手段来同步进程对共享内存的访问,否则会产生竞态条件。因此共享内存通常和其他进程间通信方式一起使用。
4个系统调用:shmget, shmat, shmdt, shmctl.

shmget

该系统调用创建一段新的共享内存,或者获取一段已经存在的共享内存。

#include<sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

key是键值,唯一表示一段共享内存,size指定共享内存的大小,单位是B,如果是新建共享内存,必须被显式指定。
shmflg参数的使用含义与semget系统调用的sem_flags参数相同。还有两个额外参数:

  • SHM_HUGETLB,类似mmap的MAP_HUGETLB标志,系统将使用大页面来为共享内存分配空间。
  • SHM_NORESERVE,类似mmap的MAP_NORESERVE,不为共享内存保留交换分区(swap空间)。这样当物理内存不足时,对该共享内存执行写操作将触发SIGSEGV信号。
    shmget调用成功时会返回正整数值:共享内存的标识符。失败时会返回-1.

如果shmget创建共享内存,那么共享内存的所有字节都会被初始化为0,与之关联的内核数据结构shmget_ds将被创建并初始化。

struct shmid_ds{
	struct ipc_perm shm_perm; // 共享内存的操作权限
	size_t shm_segsz; // 共享内存大小Byte
	__time_t shm_atime; // 最后一次shmat调用时间
	__time_t shm_dtime; // 最后一次shmdt时间
	__time_t shm_ctime; // 最后一次shmctl时间
	__pid_t shm_cpid; // 创建者的pid
	__pid_t shm_lpid; // 最后一次执行shmat或者shmdt操作的进程pid
	shmatt_t shm_nattach; // 目前关联到此共享内存的进程数量
};

在这里插入图片描述

shmat和shmdt

共享内存被创建或者获取后,我们不能立即访问它,而是需要将其关联到进程的地址空间中才可以使用shmat,完毕后还需要将其从进程地址空间中分离shmdt

#include<sys/shm.h>
void* shmat(int shm_id, const void* shm_addr, int shmflg);
int shmdt(const void* shm_addr);

其中sem_id指定共享内存标识符。shm_addr指定将共享内存关联到进程的地址空间,一般设置为NULL,使操作系统自动选择。shmflg可选标志SHM_RND会影响shm_addr。
在这里插入图片描述
shmflg还可选:

  • SHM_RDONLY:只读。不选的话默认可读可写。
  • SHM_REMAP:如果地址addr已经被关联到一段共享内存上,则重新关联
  • SHM_EXEC:它指定对共享内存的执行权限。对共享内存而言,执行权限实际上和读权限一致。
    shmat成功时返回共享内存被关联到的地址,失败则返回(void*)-1,并设置errno。成功时将修改shmid_ds部分字段:
  • 将shm_nattach加一
  • 将shm_lpid设置为调用进程的pid
  • 设置shm_atime时间

shmdt函数将关联到shm_addr处的共享内存从进程中分离。调用成功返回0,失败-1.成功时设置字段:

  • 将shm_nattach减一
  • 将shm_lpid设置为调用进程的pid
  • 设置shm_dtime时间

shmctl

#include<sys/shm.h>
int shmctl(int shm_id, int command, struct shmid_ds* buf);

在这里插入图片描述

共享内存POSIX方法

利用mmap的MAP_ANONYMOUS标志可以实现父子进程间的匿名内存共享。通过打开一个文件,mmap也可以实现无关进程间的内存共享。Linux还提供了另一种利用mmap在无关进程间共享内存的方式。其无需任何文件支持,但是需要创建或打开一个POSIX共享内存对象。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值