Linux多进程

Linux多进程

1. 进程创建

fork函数:读时共享,写时拷贝

2. GDB多进程调试

​ GDB默认只能跟踪一个进程,默认情况下gdb追踪父进程。

  • 查看当前跟踪进程
1
show follow-fork-mode
  • 设置调试父进程或者子进程:
1
set follow-fork-mode [child | parent(默认)]
  • 设置调试模式
1
set detach-on-fork [on | off]

默认为on,表示调试当前进程的时候,其他进程继续运行,如果为off,调试当前进程的时候,其他进程被gdb挂起。

  • 查看当前调试的进程
1
info inferiors
  • 切换当前调试的进程
1
inferior id
  • 使进程脱离GDB调试
1
detach inferiors id

3. exec函数族

  • exec函数族的作用是根据指定的 文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
  • 在一个进程内执行exec调用其他可执行文件,则当前进程的用户数据会被替换
  • 一般是先fork,再exec

execl

  • 头文件

    1
    #include <unistd.h>
  • 声明

    1
    int execl(const char *pathname, const char *arg, .../* (char  *) NULL */);

    传入要执行的文件的路径以及参数。

    只有调用失败才会返回-1,调用成功没有返回值。

  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <unistd.h>
    #include <stdio.h>
    int main(){
    //创建一个子进程执行exec
    pid_t pid = fork();
    if(pid > 0){
    //父进程
    printf("i am parent process pid: %d\n", getpid());
    sleep(1);
    }else if(pid == 0){
    //子进程调用hello
    execl("hello", "hello", NULL);
    //将不会被输出,因为被替换了
    printf("i am child process pid: %d\n", getpid());
    }
    for(int i = 0; i < 3; i++){
    printf("i = %d, pid = %d\n", i, getpid());
    }
    }

execlp

  • 头文件

    1
    #include <unistd.h>

    和execl不同,execlp会自动在环境变量中查找可执行程序。

  • 声明

    1
    int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
  • 实例(子进程调用ps)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <unistd.h>
    #include <stdio.h>

    int main(){
    //创建一个子进程执行exec
    pid_t pid = fork();
    if(pid > 0){
    //父进程
    printf("i am parent process pid: %d\n", getpid());
    sleep(1);
    }else if(pid == 0){
    //子进程调用ps命令
    execlp("ps", "ps", "aux", NULL);
    //将不会被输出,因为被替换了
    printf("i am child process pid: %d\n", getpid());
    }
    for(int i = 0; i < 3; i++){
    printf("i = %d, pid = %d\n", i, getpid());
    }
    }

4. 进程控制

进程退出

  • exit (标准C库)

  • _exit(系统调用)

  • 有趣的实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>

    int main(){
    printf("Hello\n");
    printf("World");
    _exit(0);//不会输出World,因为_exit不会刷新缓冲区,换成exit则可以,或者手动使用fflush刷新
    return 0;
    }

孤儿进程

  • 父进程结束但是子进程还在运行
  • 当出现孤儿进程的时候,内核就会把孤儿进程的父进程设置为init(ppid = 1),而init会循环等待,当孤儿进程结束声明周期,init进程就会释放资源善后
  • 孤儿进程并不会有什么危害

  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    //pid_t fork(void);
    int main(){
    pid_t pid = fork();
    if(pid > 0){
    printf("i am parent process, pid: %d, ppid: %d\n", getpid(), getppid());
    _exit(0);
    }else if(pid == 0){
    printf("i am child process, pid: %d, ppid: %d\n", getpid(), getppid());
    sleep(1);
    }
    for(int i = 0; i < 3; i++){
    printf("test: %d %d %d\n", i, getpid(), getppid());
    }
    return 0;
    }

僵尸进程

  • 每个进程结束之后,都会释放自己地址空间中的用户区数据,内核区的PCB没有办法自己释放,需要父进程去释放。

  • 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核之中,变成僵尸进程

  • 僵尸进程无法被kill -9杀死

  • 如果父进程不调用wait()或者waitpid(),那么保留的信息就不会释放,进程号就会一直被占用,但是进程号是有限的,如果产生大量的僵尸进程,将导致系统无法产生新的进程,危害较大,需要避免。

  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    //pid_t fork(void);
    int main(){
    pid_t pid = fork();
    if(pid > 0){
    while(1){//父进程是死循环导致子进程PCB无法被回收
    printf("i am parent process, pid: %d, ppid: %d\n", getpid(), getppid());
    sleep(1);
    }
    }else if(pid == 0){
    printf("i am child process, pid: %d, ppid: %d\n", getpid(), getppid());
    }
    for(int i = 0; i < 3; i++){
    printf("test: %d %d %d\n", i, getpid(), getppid());
    }
    return 0;
    }

5. wait

  • 每个进程退出的时候,内核会释放该进程的所有资源,包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要是指进程控制块PCB的信息(进程号,退出状态、运行时间等)。
  • 父进程可以通过调用wait或者waitpid得到它的退出状态同时彻底清除掉这个进程。
  • wait函数会阻塞,waitpid()可以设置不阻塞,waitpid()可以指定等待哪个子进程结束。
  • 一次wait或者waitpid调用只能清理一个子进程,清理多个子进程应使用循环。

wait

  • 头文件

    1
    2
    #include <sys/types.h>
    #include <sys/wait.h>
  • 声明

    1
    2
    pid_t wait(int *wstatus);//等待任意一个子进程结束,如果任意一个子进程结束,则此函数会回收子进程资源,成功返回被回收子进程的id,失败则返回-1
    pid_t waitpid(pid_t pid, int *wstatus, int options);
  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <stdio.h>
    #include <unistd.h>

    int main(){
    //一个父进程创建五个子进程
    pid_t pid;
    for(int i = 0; i < 5; i++){
    pid = fork();
    if(pid == 0) break;//子进程需要break,否则会指数级创建进程
    }
    if(pid > 0){
    while(1){
    printf("parent process, pid = %d\n", getpid());
    int ret = wait(NULL);
    printf("children die, pid = %d\n", ret);
    sleep(1);
    }
    }else if(pid == 0){
    printf("child process, pid = %d\n", getpid());
    }
    return 0;
    }

waitpid

  • 回收指定进程号的子进程,可以设置是否阻塞。

  • pid > 0回收对应的pid子进程,pid = 0,回收进程组的子进程,pid = -1, 回收任意的子进程,相当于wait, pid < -1, 回收某个进程组的id(绝对值)

  • option为0为阻塞状态,WNOHANG为非阻塞。

  • 返回值:>0返回pid, =0 表示还有子进程没结束, 等于-1表示没有子进程了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>

    int main() {

    // 有一个父进程,创建5个子进程(兄弟)
    pid_t pid;

    // 创建5个子进程
    for(int i = 0; i < 5; i++) {
    pid = fork();
    if(pid == 0) {
    break;
    }
    }

    if(pid > 0) {
    // 父进程
    while(1) {
    printf("parent, pid = %d\n", getpid());
    sleep(1);

    int st;
    // int ret = waitpid(-1, &st, 0);
    int ret = waitpid(-1, &st, WNOHANG);

    if(ret == -1) {
    break;
    } else if(ret == 0) {
    // 说明还有子进程存在
    continue;
    } else if(ret > 0) {

    if(WIFEXITED(st)) {
    // 是不是正常退出
    printf("退出的状态码:%d\n", WEXITSTATUS(st));
    }
    if(WIFSIGNALED(st)) {
    // 是不是异常终止
    printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
    }

    printf("child die, pid = %d\n", ret);
    }

    }

    } else if (pid == 0){
    // 子进程
    while(1) {
    printf("child, pid = %d\n",getpid());
    sleep(1);
    }
    exit(0);
    }

    return 0;
    }

6.进程通信(IPC)简介

  • 进程是一个独立的资源分配单元,不同进程之间的资源是独立的,不能在一个进程中直接访问另一个进程的资源。
  • 但是不同的进程需要信息的交互和状态的传递,因此需要进程间通信。
  • 进程通信目的:数据传输、通知事件、资源共享、进程控制

1.进程间通信方式

zwtpkt.png

(1)匿名管道(pipe)

UNIX系统IPC最古老的形式,所有的UNIX系统都支持这种通信机制。

  • 举例

    1
    ls | wc -l
  • 管道是一个在内核中维护的缓冲器,这个缓冲器存储能力是有限的,不同的操作系统大小不一定相同

  • 管道拥有文件的特质:读、写,匿名管道没有文件实体有名管道有文件实体,但不存储数据,可以按照操作文件的方式对管道进行操作

  • 一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块而不管写入进程写入管道的数据块的大小是多少。

  • 通过管道传递的数据是顺序的

  • 管道是半双工的

  • 匿名管道只能用于具有关系的进程通信

  • 通信示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    int main(){
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1){
    perror("pipe");
    exit(0);
    }

    pid_t pid = fork();
    if(pid > 0){
    //父进程
    printf("i am parent process pid: %d\n", getpid());
    char buf[1024] = {0};
    while(1){
    //父进程先读后写
    int len = read(pipefd[0], buf, sizeof(buf));
    printf("parent recv: %s, pid = %d\n", buf, getpid());
    char *str = "hello i am parent";
    write(pipefd[1], str, strlen(str));
    sleep(1);
    }
    }else if(pid == 0){
    //子进程
    printf("i am child process pid: %d\n", getpid());
    char buf[1024] = {0};
    while(1){
    //子进程先写后读
    char *str = "hello i am a child";
    write(pipefd[1], str, strlen(str));
    sleep(1);

    int len = read(pipefd[0], buf, sizeof(buf));
    printf("child recv: %s, pid = %d\n", buf, getpid());
    }
    }
    return 0;
    }

​ * 管道默认是阻塞的,若管道中没有数据,read阻塞,若管道满,则write阻塞

(2)有名管道(FIFO)

  • FIFO不同于匿名管道之处在于它提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,并且打开方式与打开一个文件是一样的,因此通过FIFO不相关的进程也能交换数据
  • 虽然FIFO在文件系统中作为一个文件存在 ,但是FIFO中的内容却存放在内存中。
  • 当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用

(3)内存映射(非阻塞)

  • 将磁盘数据映射到内存,用户通过修改内存就能修改磁盘文件

  • 头文件

    1
    #include <sys/mman.h>
  • 函数声明

    1
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

    功能:将一个文件或者设备的数据映射到内存中

    参数:void *addr(NULL, 由内核指定),length:要映射的数据的长度,建议使用文件的长度

    prot:对申请的内存映射区的操作权限(PROT_EXEC, PROT_READ, PROT_WRITE, PROT_EXEC)

    flags:MAP_SHARED(映射区的数据会自动地和磁盘文件同步,进程之间通信必须设置),MAP_PRIVATE(不同步,内存映射区地数据改变不会修改文件)

    fd:需要映射地文件地描述符,通过open得到,注意文件的大小不能为0,open指定的权限不能和prot参数有冲突。

    offset:文件的偏移量,一般不使用,必须是4k的整数倍。

  • 函数声明

    1
    int munmap(void *addr, size_t length);

    功能:释放内存映射

  • 通过共享内存实现进程通信

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    /*
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    int munmap(void *addr, size_t length);

    */
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <string.h>
    #include <stdlib.h>
    #include <wait.h>

    int main(){
    int fd = open("test.txt", O_RDWR);

    int size = lseek(fd, 0, SEEK_END);

    void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(ptr == MAP_FAILED){
    perror("mmap");
    exit(0);
    }

    pid_t pid = fork();
    if(pid > 0){
    wait(NULL);//释放子进程
    char buf[64];
    strcpy(buf, (char *)ptr);
    printf("read data %s\n", buf);
    }else if(pid == 0){
    strcpy((char *)ptr, "hello, daddy");
    }
    munmap(ptr, size);
    return 0;
    }
    • 内存映射实现文件复制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    int main(){
    int fd = open("src.txt", O_RDWR);
    if(fd == -1){
    perror("open");
    exit(0);
    }

    int fd2 = open("des.txt", O_CREAT | O_RDWR, 0664);
    if(fd2 == -1){
    perror("open");
    exit(0);
    }
    int siz = lseek(fd, 0, SEEK_END);
    truncate("des.txt", siz);

    void* ptr1 = mmap(NULL, siz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    void* ptr2 = mmap(NULL, siz, PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0);
    if(ptr1 == MAP_FAILED || ptr2 == MAP_FAILED){
    perror("mmap");
    exit(0);
    }
    memcpy(ptr2, ptr1, siz);

    munmap(ptr1, siz);
    munmap(ptr2, siz);

    close(fd);
    close(fd2);
    }

(4)信号

  • 信号是Linux进程通信最古老的方式之一,也成为软件中断,是在软件层次对中断机制的一种模拟。

  • 发往进程的诸多信号,通常都源自于内核

    (1)对于前台进程,可以通过输入特殊的终端字符来发送信号,如ctrl+c

    (2)硬件发生异常,硬件检测到了错误并通知内核,随机内核发送信号给相关进程

    (3)系统状态变化,如alarm定时器到期将引起SIGALARM信号

    (4)运行kill命令或者调用kill函数

  • kill -l查看linux系统的信号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP    
    6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
    11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
    16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
    21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
    26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
    31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
    38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
    43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
    48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
    53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
    58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
    63) SIGRTMAX-1 64) SIGRTMAX
  • 一些需要掌握的信号

    (1)SIGINT 当用户按下,终止进程

    (2)SIGQUIT, 当用户按下,终止进程

    (3)SIGKILL,终止进程,可以杀死任何进程

    (4)SIGSEGV,进程进行了无效的内存访问(段错误),终止进程并产生core文件

    (5)SIGPIPE,管道破裂,终止进程

    (6)SIGCHLD, 子进程结束时,父进程会收到该信号

    (7)SIGCONT,如果进程已经停止,则使其继续执行

    (8)SIGSTOP,停止进程的执行,信号不能被忽略。

  • 进程接收到信号后的默认处理

    (1)Term 终止进程

    (2)Ign 忽略

    (3)Core 终止进程

    (4)Stop 暂停进程

    (5)Cont 继续执行

  • kill函数

    头文件

    1
    2
    #include <sys/types.h>
    #include <signal.h>

    函数声明

    1
    int kill(pid_t pid, int sig);

    向进程号为pid的进程发送sig信号。

    pid > 0: 发送给指定进程

    pid = 0: 将信号发送给当前的进程组

    pid = -1:将信号发送给每一个有权限接受这个信号的进程

    pid < -1: 这个pid = 某个进程组的id取反

  • raise函数

    头文件

    1
    #include <signal.h>

    函数声明

    1
    int raise(int sig);

    给当前进程发送sig信号

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include <sys/types.h>
    #include <signal.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>

    int main(){
    pid_t pid = fork();
    if(pid == -1){
    perror("fork");
    exit(0);
    }
    if(pid > 0){
    printf("i am parent process\n");
    sleep(10);
    kill(pid, SIGINT);//pid是子进程的id
    printf("child process is killed\n");
    }else if(pid == 0){
    while(1){
    printf("i am child process\n");
    sleep(1);
    }
    }
    }
  • abort函数

    头文件

    1
    #include <stdlib.h>

    函数声明

    1
    void abort(void);

    终止当前进程。

  • alarm函数

    头文件

    1
    #include <unistd.h>

    函数声明

    1
    unsigned int alarm(unsigned int seconds);
    • 计时函数,在第seconds秒向进程发送一个SIGALRM信号

    • 每个进程最多只有一个alarm

    • 第一次调用alarm返回0, 后续返回剩余的

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      #include <unistd.h>
      #include <stdio.h>

      int main(){
      int seconds = alarm(5);
      printf("%d\n", seconds);//seconds = 0;

      sleep(2);
      seconds = alarm(10);//seconds = 3;
      printf("%d\n", seconds);
      }
  • setitimer函数

    • 头文件

      1
      #include <sys/time.h>
* 函数声明

  
1
2
3
4
5
6
7
8
9
10
11
12
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
struct itimerval {
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
};

struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
* 参数 (1)which: 定时器什么时间开始定时, ITIMER_REAL(时间到达发送SIGALARM,常用), ITIMER_VIRTUAL, ITIMER_PROF (2)new_value: 时间间隔结构体 * 从某一时刻开始,周期性的定时,需要捕捉信号。
  • 信号捕捉signal函数

    • 头文件

      1
      #include <signal.h>
* 函数声明

  
1
2
typedef void (*sighandler_t)(int);//int是捕捉到的信号
sighandler_t signal(int signum, sighandler_t handler);
* 参数说明:signum(需要捕捉的信号), handler(捕捉到后的操作) * handler (1)SIG_IGN(忽略信号) (2)SIG_DFL(使用信号默认的行为) (3)回调函数,传入函数指针
  • 案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include <sys/time.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    void myalarm(int num){
    printf("捕捉到信号: %d\n", num);

    }
    int main(){
    //注册信号捕捉
    // signal(SIGALRM, SIG_IGN);//程序不会停止
    //signal(SIGALRM, SIG_DFL);//程序10s停止
    signal(SIGALRM, myalarm);//程序在第10s捕捉到SIGALRM信号,以后每隔2s捕捉到1次
    struct itimerval new_value;
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;

    new_value.it_value.tv_sec = 10;
    new_value.it_value.tv_usec = 0;

    int ret = setitimer(ITIMER_REAL, &new_value, NULL);
    if(ret == -1){
    perror("setitimer");
    exit(0);
    }
    printf("start....\n");
    getchar();
    return 0;
    }
    • 信号集

      进程的PCB块中存储了两个集合,阻塞信号集以及未决信号集合,当信号被创建,则被加入到未决信号集,并判断是否在阻塞信号集中,如果在阻塞信号集,则该信号阻塞吗,否则该信号被处理。

      (5)共享内存

  • 两个或者多个进程共享物理内存的一块区域(通常称为段)

  • 步骤

    调用shmget创建一个新共享内存段或者取得一个既有共享内存段的标识符

    使用shmat来附上共享内存段,使得该段成为调用进程的虚拟内存的一部分

    为引用这块内存,程序需要使用由shamt调用返回的addr值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针

    调用shmdt来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存了,这一步是可选的,并且在进程终止时会自动完成这一步。

    调用shmctl来删除共享内存段,只有当当前所有附加内存段的进程都与之分离后内存段才会销毁,只有一个进程需要执行这一步。

  • shmget函数

    1
    2
    3
    4
    5
    6
    7
    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg);
    //key是创建标识符
    //size是内存大小
    //shmlfg: 访问权限,附加属性:创建/判断共享内存是否存在
    //返回值:返回共享内存的标识符,失败返回-1
  • shmat函数

    1
    2
    3
    4
    5
    6
    7
    8
    #include <sys/types.h>
    #include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    //shmid是共享内存标识符
    //shmaddr是共享内存的起始地址,指定为NULL
    //访问权限
    读:SHM_RDONLY, 必须要有读权限
    读写:0
  • shmdt函数

    1
    2
    3
    4
    5
    #include <sys/types.h>
    #include <sys/shm.h>
    int shmdt(const void *shmaddr);//将共享内存与进程分离
    //shmaddr是共享内存的首地址
    //成功返回0, 失败返回-1
  • shmctl函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <sys/ipc.h>
    #include <sys/shm.h>

    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    //对共享内存进行操作。删除共享内存,共享内存要删除才会消失
    //shmid:共享内存的id
    //cmd: 要做的操作
    IPC_STAT: 获取共享内存的当前的状态
    IPC_SET: 设置共享内存的状态
    IPC_RMID: 标记共享内存被销毁
    //buf:需要设置或者获取的共享内存的属性信息
    IPC_STAT:buf存储数据
    IPC_SET: buf需要初始化数据,设置到内核中
    IPC_RMID:NULL
  • 通信示例,write.c和read.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <string.h>
    int main(){
    //创建共享内存
    int shmid = shmget(100, 4096, IPC_CREAT | 0664);
    //关联, ptr是共享内存虚拟地址的首地址
    void *ptr = shmat(shmid, NULL, 0);
    //写入共享内存
    char * str = "hello world";
    memcpy(ptr, str, strlen(str) + 1);
    printf("按任意键进行");
    getchar();
    //解除关联
    shmdt(ptr);
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>

    int main(){
    //获取共享内存
    int shmid = shmget(100, 4096, IPC_CREAT);
    //关联, ptr是共享内存虚拟地址的首地址
    void *ptr = shmat(shmid, NULL, 0);
    //读取共享内存
    printf("%s\n", (char *)ptr);
    printf("按任意键进行");
    getchar();
    //解除关联
    shmdt(ptr);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
    }
  • ftok函数

    1
    2
    3
    4
    5
    #include <sys/types.h>
    #include <sys/ipc.h>

    key_t ftok(const char *pathname, int proj_id);
    //根据pathname和proj_id生成一个共享内存的key
  • 共享内存和内存映射的区别

    1. 共享内存可以直接创建,而内存映射需要磁盘文件(匿名映射除外)

    2. 共享内存效率更高

    3. 内存

      共享内存是所有进程都操作同一块内存,而内存映射是每个进程在自己的虚拟地址空间中有一个独立的内存

    4. 数据安全:进程突然退出,共享内存还存在但是内存映射会消失;如果电脑宕机,数据存在共享内存中就没有了,而内存映射区的数据还在。