zoukankan      html  css  js  c++  java
  • 高级Linux程序设计第五章:进程间通信

    • 五种进程间通信的方式:

      • 共享内存(shared memory):其允许多个进程通过读写同一块内存地址来相互通信。

      • 内存映射(Mapped memory):其和共享内存相似,然而它是和文件系统上的一个文件相关联的。

      • 管道(Pipe):其允许一个进程到另一个相关进程的顺序通信。

      • 先入先出队列(FIFO):和管道类似,然而因为其对应于文件系统上的文件名,可以在两个不相关的进程间通信。

      • Socket:其允许在不同的计算机上的不同进程间通信。

    1、共享内存(Shared Memory)

    • 共享内存时进程间通信方式中最快的一种,因为进程是共享同一块内存。

    • 内核并不提供对共享内存访问的同步机制,因而必须自己提供同步方式。

    • 要用共享内存块,需要一个进程首先分配此内存块。

    • 欲访问共享内存块的进程必须要连接到此内存块。

    • 在使用完共享内存块的时候,进程必须要卸载此内存块。

    • 需要有一个进程释放此内存块。

    • 所有的共享内存块都是以4KB的整数倍分配。

    1.1、分配

    • 进程用函数shmget分配一个共享内存块。

      • 第一个参数是共享内存块的key

        • 不同的进程可以根据此key来访问同一个共享内存块。

        • 使用IPC_PRIVATE作为key会保证创建一个新的共享内存块。

        • 如果多个进程访问同一个共享内存块,则必须用同一个key。

      • 第二个参数表示内存块的大小。

      • 第三个参数是一系列标志位:

        • IPC_CREAT创建一个新的内存块。

        • IPC_EXCL此标志位和IPC_CREAT一起使用。如果key已经存在,则此标志位使得shmget失败。

    1.2、连接(Attachment )和卸载(Detachment)

    • 一个进程需要调用shmat来连接一个共享内存。

      • 第一个参数是共享内存块的id,由shmget返回。

      • 第二个参数是一个指针,其指向共享内存块映射的内存地址,如果是NULL,则系统会自动选择一个可用的内存地址。

      • 第三个参数是标志位:

        • SHM_RND表示第二个参数所指定的地址必须同页的大小对齐。

        • SHM_RDONLY表示此内存块只读。

      • 此函数返回值是连接的共享内存的起始地址。

    • 共享内存块可用函数shmdt卸载,应传给它共享内存块的起始地址。

    • 调用exit及exec函数自动卸载共享内存块。

    1.3、控制和释放共享内存块

    • shmctl函数可用返回和修改共享内存块的信息。

      • 第一个参数是共享内存块id

      • 欲得到一个共享内存块的信息,第二个参数设为IPC_STAT,第三个参数是指向shmid_ds结构体的指针。

      • 欲删除一个共享内存块,第二个参数设为IPC_RMID,第三个参数设为NULL。

    • 一个共享内存块在使用结束后,必须用shmctl显式的释放。

    • 调用exit和exec自动卸载共享内存块,但是不释放。

    #include <stdio.h>

    #include <sys/shm.h>

    #include <sys/stat.h>

    int main ()

    {

        int segment_id;

        char* shared_memory;

        struct shmid_ds shmbuffer;

        int segment_size;

        const int shared_segment_size = 0x6400;

        /* Allocate a shared memory segment. */

        segment_id = shmget (IPC_PRIVATE, shared_segment_size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);

        /* Attach the shared memory segment. */

        shared_memory = (char*) shmat (segment_id, 0, 0);

        printf (“shared memory attached at address %p\n”, shared_memory);

        /* Determine the segment’s size. */

        shmctl (segment_id, IPC_STAT, &shmbuffer);

        segment_size = shmbuffer.shm_segsz;

        printf (“segment size: %d\n”, segment_size);

        /* Write a string to the shared memory segment. */

        sprintf (shared_memory, “Hello, world.”);

        /* Detach the shared memory segment. */

        shmdt (shared_memory);

        /* Reattach the shared memory segment, at a different address. */

        shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0);

        printf (“shared memory reattached at address %p\n”, shared_memory);

        /* Print out the string from shared memory. */

        printf (“%s\n”, shared_memory);

        /* Detach the shared memory segment. */

        shmdt (shared_memory);

        /* Deallocate the shared memory segment. */

        shmctl (segment_id, IPC_RMID, 0);

        return 0;

    ipcs命令可用查看进程间通信机制的信息

    使用-m可查看共享内存的信息

    % ipcs -m

    ------ Shared Memory Segments --------

    key shmid owner perms bytes nattch status

    0x00000000 1627649 user 640 25600 0

    ipcrm命令可删除进程间通信对象.

    % ipcrm shm 1627649

    [liuchao@localhost ~]$ ipcs

    ------ Shared Memory Segments --------

    key shmid owner perms bytes nattch status

    0x00000000 196608 liuchao 600 393216 2 dest

    0x764867bd 65537 liuchao 600 1 0

    0x2c0056d5 98306 liuchao 600 1 0

    0x500e7827 131075 liuchao 600 1 0

    0x20e0f21d 163844 liuchao 600 1 0

    0x00000000 229381 liuchao 600 393216 2 dest

    0x00000000 262150 liuchao 600 393216 2 dest

    0x00000000 294919 liuchao 600 393216 2 dest

    0x00000000 327688 liuchao 600 393216 2 dest

    0x00000000 360457 liuchao 600 393216 2 dest

    0x00000000 393226 liuchao 600 393216 2 dest

    0x00000000 425995 liuchao 600 393216 2 dest

    0x00000000 458764 liuchao 600 393216 2 dest

    0x00000000 491533 liuchao 600 393216 2 dest

    0x00000000 557070 liuchao 600 393216 2 dest

    0x00000000 589839 liuchao 600 393216 2 dest

    ------ Semaphore Arrays --------

    key semid owner perms nsems

    0x59d9bc4a 0 liuchao 600 1

    0x3bd464f2 32769 liuchao 600 1

    ------ Message Queues --------

    key msqid owner perms used-bytes messages

    2、进程信号量

    2.1、分配(Allocation)和释放(Deallocation)

    • 调用semget分配一个信号量,调用semctl来释放一个信号量。

    • semget的参数为一个信号量集的key,信号量集中的信号量的个数,权限标志位,返回值为信号量集id。

    • semctl的参数为信号量集的id,信号量集中的信号量的个数,IPC_RMID。

    • 当所有的使用信号量的进程结束后,信号量仍然存在。

    • 最后一个使用信号量集的进程必须显式的删除它。

    #include <sys/ipc.h>

    #include <sys/sem.h>

    #include <sys/types.h>

    /* We must define union semun ourselves. */

    union semun {

        int val;

        struct semid_ds *buf;

        unsigned short int *array;

        struct seminfo *__buf;

    };

    /* Obtain a binary semaphore’s ID, allocating if necessary. */

    int binary_semaphore_allocation (key_t key, int sem_flags)

    {

        return semget (key, 1, sem_flags);

    }

    /* Deallocate a binary semaphore. All users must have finished their

    use. Returns -1 on failure. */

    int binary_semaphore_deallocate (int semid)

    {

        union semun ignored_argument;

        return semctl (semid, 1, IPC_RMID, ignored_argument);

    }

    2.2、初始化信号量

    #include <sys/types.h>

    #include <sys/ipc.h>

    #include <sys/sem.h>

    /* We must define union semun ourselves. */

    union semun {

        int val;

        struct semid_ds *buf;

        unsigned short int *array;

        struct seminfo *__buf;

    };

    /* Initialize a binary semaphore with a value of 1. */

    int binary_semaphore_initialize (int semid)

    {

        union semun argument;

        unsigned short values[1];

        values[0] = 1;

        argument.array = values;

        return semctl (semid, 0, SETALL, argument);

    }

    2.3、Wait和Post操作

    • semop函数支持wait和post操作。

      • 第一个参数是信号量集id。

      • 第二个参数是一个sembuf结构体的数组。

      • 第三个参数是数组的长度。

    • sembuf结构体:

      • sem_num是信号量集中作为操作对象的信号量的号。

      • sem_op表示对信号量的操作。如果sem_op是正数,则其将被加到信号量的值上。如果sem_op是负数,则得到其绝对值,如果此值能够使得信号量的值为负,则阻塞当前线程,直到此信号量的值等于sem_op的绝对值。如果sem_op为零,阻塞当前线程,直到信号量的值为零。

      • sem_flg是标志位,IPC_NOWAIT使得此操作不会被阻塞,SEM_UNDO表示当进程结束的时候,系统自动取消此次操作。

    #include <sys/types.h>

    #include <sys/ipc.h>

    #include <sys/sem.h>

    /* Wait on a binary semaphore. Block until the semaphore value is positive, then

    decrement it by 1. */

    int binary_semaphore_wait (int semid)

    {

        struct sembuf operations[1];

        /* Use the first (and only) semaphore. */

        operations[0].sem_num = 0;

        /* Decrement by 1. */

        operations[0].sem_op = -1;

        /* Permit undo’ing. */

        operations[0].sem_flg = SEM_UNDO;

        return semop (semid, operations, 1);

    }

    /* Post to a binary semaphore: increment its value by 1.

    This returns immediately. */

    int binary_semaphore_post (int semid)

    {

        struct sembuf operations[1];

        /* Use the first (and only) semaphore. */

        operations[0].sem_num = 0;

        /* Increment by 1. */

        operations[0].sem_op = 1;

        /* Permit undo’ing. */

        operations[0].sem_flg = SEM_UNDO;

        return semop (semid, operations, 1);

    }

    3、内存映射(Mapped Memory)

    3.1、映射一个普通文件

    • 使用mmap函数可将一个普通文件映射到进程内存中。

      • 第一个参数是文件将映射到的内存地址,NULL使得Linux自动选择一个可用的地址。

      • 第二个参数是映射的长度。

      • 第三个参数是映射的内存的保护模式:PROT_READ,PROT_WRITE,PROT_EXEC。

      • 第四个参数是一个标志位:

        • MAP_FIXED表示映射的内存地址必须和页对齐。

        • MAP_PRIVATE表示写入映射的内存的数据不会写入关联的文件,而是写入另一个文件副本,对其他线程不可见。

        • MAP_SHARED表示写入映射的内存的数据会立即写入关联的文件,不会有缓存。

      • 第五个参数是关联文件的文件描述符。

      • 第六个参数是映射的文件的偏移量。

    (mmap-write.c) Write a Random Number to a Memory-Mapped File

    #include <stdlib.h>

    #include <stdio.h>

    #include <fcntl.h>

    #include <sys/mman.h>

    #include <sys/stat.h>

    #include <time.h>

    #include <unistd.h>

    #define FILE_LENGTH 0x100

    /* Return a uniformly random number in the range [low,high]. */

    int random_range (unsigned const low, unsigned const high)

    {

        unsigned const range = high - low + 1;

        return low + (int) (((double) range) * rand () / (RAND_MAX + 1.0));

    }

    int main (int argc, char* const argv[])

    {

        int fd;

        void* file_memory;

        /* Seed the random number generator. */

        srand (time (NULL));

        /* Prepare a file large enough to hold an unsigned integer. */

        fd = open (argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);

        lseek (fd, FILE_LENGTH+1, SEEK_SET);

        write (fd, “”, 1);

        lseek (fd, 0, SEEK_SET);

        /* Create the memory mapping. */

        file_memory = mmap (0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0);

        close (fd);

        /* Write a random integer to memory-mapped area. */

        sprintf((char*) file_memory, “%d\n”, random_range (-100, 100));

        /* Release the memory (unnecessary because the program exits). */

        munmap (file_memory, FILE_LENGTH);

        return 0;

    }

    (mmap-read.c) Read an Integer from a Memory-Mapped File, and Double It

    #include <stdlib.h>

    #include <stdio.h>

    #include <fcntl.h>

    #include <sys/mman.h>

    #include <sys/stat.h>

    #include <unistd.h>

    #define FILE_LENGTH 0x100

    int main (int argc, char* const argv[])

    {

        int fd;

        void* file_memory;

        int integer;

        /* Open the file. */

        fd = open (argv[1], O_RDWR, S_IRUSR | S_IWUSR);

        /* Create the memory mapping. */

        file_memory = mmap (0, FILE_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

        close (fd);

        /* Read the integer, print it out, and double it. */

        sscanf (file_memory, “%d”, &integer);

        printf (“value: %d\n”, integer);

        sprintf ((char*) file_memory, “%d\n”, 2 * integer);

        /* Release the memory (unnecessary because the program exits). */

        munmap (file_memory, FILE_LENGTH);

        return 0;

    }

    3.2、共同访问一个文件

    • 不同的进程可以通过将同一个文件映射到内存来通信。

    • 设置MAP_SHARD使得写入到映射的内存的数据会立即写入关联的文件,并对另一个文件可见。

    • 如果不做以上设定,则Linux会对数据进行缓存,可以用函数msync将缓存写入文件。

      • 前两个参数表示映射的内存块。

      • 第三个参数是标志位:

        • MS_ASTYNC:写缓存并不立即执行。

        • MS_SYNC:写缓存立即执行。

        • MS_INVALIDATE:所有的文件映射都被刷新,可以看到最新的更新。

    msync (mem_addr, mem_length, MS_SYNC | MS_INVALIDATE);

    • 设置MAP_PRIVATE将创建一个写即复制的映射区。写入这些映射区的数据仅仅在当前进程可见,对其他进程不可见。

    4、管道(Pipes)

    4.1、创建管道

    int pipe_fds[2];

    int read_fd;

    int write_fd;

    pipe (pipe_fds);

    read_fd = pipe_fds[0];

    write_fd = pipe_fds[1];

    4.2、用管道来进行子进程和父进程之间的通信

    #include <stdlib.h>

    #include <stdio.h>

    #include <unistd.h>

    /* Write COUNT copies of MESSAGE to STREAM, pausing for a second between each. */

    void writer (const char* message, int count, FILE* stream)

    {

        for (; count > 0; --count) {

            /* Write the message to the stream, and send it off immediately. */

            fprintf (stream, “%s\n”, message);

            fflush (stream);

            /* Snooze a while. */

            sleep (1);

        }

    }

    /* Read random strings from the stream as long as possible. */

    void reader (FILE* stream)

    {

        char buffer[1024];

        /* Read until we hit the end of the stream. fgets reads until either a newline or the end-of-file. */

        while (!feof (stream) && !ferror (stream) && fgets (buffer, sizeof (buffer), stream) != NULL)

            fputs (buffer, stdout);

    }

    int main ()

    {

        int fds[2];

        pid_t pid;

        /* Create a pipe. File descriptors for the two ends of the pipe are placed in fds. */

        pipe (fds);

        /* Fork a child process. */

        pid = fork ();

        if (pid == (pid_t) 0) {

            FILE* stream;

            /* This is the child process. Close our copy of the write end of the file descriptor. */

            close (fds[1]);

            /* Convert the read file descriptor to a FILE object, and read from it. */

            stream = fdopen (fds[0], “r”);

            reader (stream);

            close (fds[0]);

        }

        else {

            /* This is the parent process. */

            FILE* stream;

            /* Close our copy of the read end of the file descriptor. */

            close (fds[0]);

            /* Convert the write file descriptor to a FILE object, and write to it. */

            stream = fdopen (fds[1], “w”);

            writer (“Hello, world.”, 5, stream);

            close (fds[1]);

        }

        return 0;

    }

    4.3、用管道重定向标准输入,标准输出,错误流。

    #include <stdio.h>

    #include <sys/types.h>

    #include <sys/wait.h>

    #include <unistd.h>

    int main ()

    {

        int fds[2];

        pid_t pid;

        /* Create a pipe. File descriptors for the two ends of the pipe are placed in fds. */

        pipe (fds);

        /* Fork a child process. */

        pid = fork ();

        if (pid == (pid_t) 0) {

            /* This is the child process. Close our copy of the write end of the file descriptor. */

            close (fds[1]);

            /* Connect the read end of the pipe to standard input. */

            dup2 (fds[0], STDIN_FILENO);

            /* Replace the child process with the “sort” program. */

            execlp (“sort”, “sort”, 0);

        }

        else {

            /* This is the parent process. */

            FILE* stream;

            /* Close our copy of the read end of the file descriptor. */

            close (fds[0]);

            /* Convert the write file descriptor to a FILE object, and write to it. */

            stream = fdopen (fds[1], “w”);

            fprintf (stream, “This is a test.\n”);

            fprintf (stream, “Hello, world.\n”);

            fprintf (stream, “My dog has fleas.\n”);

            fprintf (stream, “This program is great.\n”);

            fprintf (stream, “One fish, two fish.\n”);

            fflush (stream);

            close (fds[1]);

            /* Wait for the child process to finish. */

            waitpid (pid, NULL, 0);

       }

       return 0;

    }

    4.4、打开(popen)和关闭(pclose)管道

    #include <stdio.h>

    #include <unistd.h>

    int main ()

    {

        FILE* stream = popen (“sort”, “w”);

        fprintf (stream, “This is a test.\n”);

        fprintf (stream, “Hello, world.\n”);

        fprintf (stream, “My dog has fleas.\n”);

        fprintf (stream, “This program is great.\n”);

        fprintf (stream, “One fish, two fish.\n”);

        return pclose (stream);

    }

    4.5、先进先出队列(FIFOs)

    • 一个先进先出队列是一个管道,只不过在文件系统中有文件名与之对应。

    • FIFOs又被称为命名管道。

    • mkfifo命令可以创建一个FIFO

    % mkfifo /tmp/fifo

    % ls -l /tmp/fifo

    prw-rw-rw- 1 samuel users 0 Jan 16 14:04 /tmp/fifo

    • mkfifo函数可以创建一个FIFO

    • 第一个参数是文件系统中的路径。

    • 第二个参数是权限。

    • 访问FIFO和访问一个普通文件相同。

    • 如果两个进程通过FIFO进行通信,则需要一个进程打开一个FIFO用于写,另一个进程打开同一个FIFO用于读。

    5、套接字(Sockets)

    • 创建一个套接字:

      • 命名空间:PF_LOCAL和PF_UNIX表示本地命名空间,PF_INET表示互联网命名空间。

      • 通信方式:SOCK_STREAM表示面向连接的套接字,SOCK_DGRAM表示面向数据报的套接字。

    • 关闭套接字:close

    • 连接套接字:欲在客户端和服务器段建立连接,客户端调用connect,指向服务器的地址,服务器端等待accept连接。

    • 绑定套接字:bind,将套接字绑定到一个地址。

    • 监听套接字:listen,使得服务器监听一个端口,等待accept一个连接。

    • 接受套接字:accept,接受一个来自客户端的连接。

    5.1、本地命名空间套接字

    访问同一台机器的套接字可以使用本地命名空间:PF_LOCAL和PF_UNIX

    (socket-server.c) Local Namespace Socket Server

    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    #include <sys/socket.h>

    #include <sys/un.h>

    #include <unistd.h>

    /* Read text from the socket and print it out. Continue until the

    socket closes. Return nonzero if the client sent a “quit”

    message, zero otherwise. */

    int server (int client_socket)

    {

        while (1) {

            int length;

            char* text;

            /* First, read the length of the text message from the socket. If read returns zero, the client closed the connection. */

            if (read (client_socket, &length, sizeof (length)) == 0)

                return 0;

            /* Allocate a buffer to hold the text. */

            text = (char*) malloc (length);

            /* Read the text itself, and print it. */

            read (client_socket, text, length);

            printf (“%s\n”, text);

            /* Free the buffer. */

            free (text);

            /* If the client sent the message “quit,” we’re all done. */

            if (!strcmp (text, “quit”))

                return 1;

        }

    }

    int main (int argc, char* const argv[])

    {

        const char* const socket_name = argv[1];

        int socket_fd;

        struct sockaddr_un name;

        int client_sent_quit_message;

        /* Create the socket. */

        socket_fd = socket (PF_LOCAL, SOCK_STREAM, 0);

        /* Indicate that this is a server. */

        name.sun_family = AF_LOCAL;

        strcpy (name.sun_path, socket_name);

        bind (socket_fd, &name, SUN_LEN (&name));

        /* Listen for connections. */

        listen (socket_fd, 5);

        /* Repeatedly accept connections, spinning off one server() to deal with each client. Continue until a client sends a “quit” message. */

        do {

            struct sockaddr_un client_name;

            socklen_t client_name_len;

            int client_socket_fd;

            /* Accept a connection. */

            client_socket_fd = accept (socket_fd, &client_name, &client_name_len);

            /* Handle the connection. */

            client_sent_quit_message = server (client_socket_fd);

            /* Close our end of the connection. */

            close (client_socket_fd);

        } while (!client_sent_quit_message);

        /* Remove the socket file. */

        close (socket_fd);

        unlink (socket_name);

        return 0;

    }

    (socket-client.c) Local Namespace Socket Client

    #include <stdio.h>

    #include <string.h>

    #include <sys/socket.h>

    #include <sys/un.h>

    #include <unistd.h>

    /* Write TEXT to the socket given by file descriptor SOCKET_FD. */

    void write_text (int socket_fd, const char* text)

    {

        /* Write the number of bytes in the string, including NUL-termination. */

        int length = strlen (text) + 1;

        write (socket_fd, &length, sizeof (length));

        /* Write the string. */

        write (socket_fd, text, length);

    }

    int main (int argc, char* const argv[])

    {

        const char* const socket_name = argv[1];

        const char* const message = argv[2];

        int socket_fd;

        struct sockaddr_un name;

        /* Create the socket. */

        socket_fd = socket (PF_LOCAL, SOCK_STREAM, 0);

        /* Store the server’s name in the socket address. */

        name.sun_family = AF_LOCAL;

        strcpy (name.sun_path, socket_name);

        /* Connect the socket. */

        connect (socket_fd, &name, SUN_LEN (&name));

        /* Write the text on the command line to the socket. */

        write_text (socket_fd, message);

        close (socket_fd);

        return 0;

    }

    5.2、互联网套接字

    (socket-inet.c) Read from a WWW Server

    #include <stdlib.h>

    #include <stdio.h>

    #include <netinet/in.h>

    #include <netdb.h>

    #include <sys/socket.h>

    #include <unistd.h>

    #include <string.h>

    /* Print the contents of the home page for the server’s socket. Return an indication of success. */

    void get_home_page (int socket_fd)

    {

        char buffer[10000];

        ssize_t number_characters_read;

        /* Send the HTTP GET command for the home page. */

        sprintf (buffer, “GET /\n”);

        write (socket_fd, buffer, strlen (buffer));

        /* Read from the socket. The call to read may not

        return all the data at one time, so keep trying until we run out. */

        while (1) {

            number_characters_read = read (socket_fd, buffer, 10000);

            if (number_characters_read == 0)

                return;

            /* Write the data to standard output. */

            fwrite (buffer, sizeof (char), number_characters_read, stdout);

        }

    }

    int main (int argc, char* const argv[])

    {

        int socket_fd;

        struct sockaddr_in name;

        struct hostent* hostinfo;

        /* Create the socket. */

        socket_fd = socket (PF_INET, SOCK_STREAM, 0);

        /* Store the server’s name in the socket address. */

        name.sin_family = AF_INET;

        /* Convert from strings to numbers. */

        hostinfo = gethostbyname (argv[1]);

        if (hostinfo == NULL)

            return 1;

        else

            name.sin_addr = *((struct in_addr *) hostinfo->h_addr);

        /* Web servers use port 80. */

        name.sin_port = htons (80);

        /* Connect to the Web server */

        if (connect (socket_fd, &name, sizeof (struct sockaddr_in)) == -1) {

            perror (“connect”);

            return 1;

        }

        /* Retrieve the server’s home page. */

        get_home_page (socket_fd);

        return 0;

    }

  • 相关阅读:
    .net core 3.1 添加mysql ef core
    使用HttpContext.SignInAsync实现简单的授权
    linux部署harbor和基本使用
    委托,事件,Action , Func
    .net core 第三方Microsoft账号登录
    Persist Security Info 参数的作用
    SQL Server2008附加数据库之后显示为只读时解决方法
    Oracle 数据库学习笔记(五)(数据库修改密码(表密码,sys密码,system密码))
    JS做”返回顶部”按钮功能并实现滑动效果
    SQL Server 2008 允许远程链接 解决方法
  • 原文地址:https://www.cnblogs.com/forfuture1978/p/1723417.html
Copyright © 2011-2022 走看看