Introduction to interprocess communication (IPC)

Posted by swallace on Wed, 09 Feb 2022 00:45:55 +0100

I Why do I need interprocess communication

1). data transmission

One process needs to send its data to another process.

2). resource sharing

Multiple processes share the same resources.

3). Notification event

A process needs to send a message to another process or group of processes to inform them of an event.

4). Process control

Some processes want to fully control the execution of another process (such as the Debug process). The control process wants to be able to intercept all operations of another process and know its state changes in time.

II What is interprocess communication

First, learn a few nouns:

1. Process isolation

Process isolation is A group of different hardware and software technologies designed to protect processes in the operating system from interference. This technique is used to prevent process A from writing to process B. The implementation of process isolation uses virtual address space. The virtual address of process A is different from that of process B, which prevents process A from writing data information to process B.

2. Virtual address space

For 32-bit systems, when a process is created, the operating system will allocate a 4GB virtual process address space for the process. The reason why it is 4GB is that in a 32-bit operating system, the length of a pointer is 4 bytes, and the addressing capacity of a 4-byte pointer ranges from 0x00000000 to 0xFFFFFFFF. The maximum value 0xFFFFFF represents the capacity of 4GB. Compared with the virtual address space, there is also a physical address space, which corresponds to the real physical memory. It should be noted that this 4GB address space is "virtual" and does not really exist. Moreover, each process can only access the data in its own virtual address space and cannot access the data in other processes. This method realizes the address isolation between processes.

For Linux operating system, the highest 1G bytes (from virtual address 0xC0000000 to 0xFFFFFF) are used by the kernel, which is called kernel space, while the lower 3G bytes (from virtual address 0x00000000 to 0xbfffff) are used by various processes, which is called user space. Each process can enter the kernel through system calls. In the Linux system, the user space of the process is independent, while the kernel space is shared. When the process switches, the user space switches and the kernel space remains unchanged.

The purpose of creating virtual address space is to solve the problem of process address space isolation. But if the program wants to execute, it must run on the real memory. Therefore, a mapping relationship must be established between the virtual address and the physical address. In this way, through the mapping mechanism, when a program accesses an address value in the virtual address space, it is equivalent to accessing another value in the physical address space. People think of a segmentation and paging method. Its idea is to make one-to-one mapping between virtual address space and physical address space. This idea is not difficult to understand. The operating system ensures that the address space of different processes is mapped to different areas in the physical address space, so that the physical address space eventually accessed by each process is separated from each other. In this way, address isolation between processes is realized.

InterProcess Communication (IPC) refers to the dissemination or exchange of information between different processes.

III IPC communication principle

Each process has a different user address space, and the global variables of any process cannot be seen in another process. Therefore, to exchange data between processes, you must pass through the kernel and open a buffer in the kernel. Process 1 copies the data from the user space to the kernel buffer, and process 2 reads the data from the kernel buffer, This mechanism provided by the kernel is called interprocess communication mechanism. The usual practice is that the message sender stores the data to be sent in the memory buffer and enters the kernel state through system call. Then the kernel program allocates memory in the kernel space, opens up a kernel cache, and calls copy in the kernel space_ from_ The user() function copies the data from the memory buffer in user space to the kernel buffer in kernel space. Similarly, when receiving data, the receiving process opens up a memory buffer in its own user space, and then the kernel program calls copy_ to_ The user() function copies the data from the kernel cache to the user space memory cache of the receiving process. In this way, the data sender process and the data receiver process complete a data transmission, which we call an inter process communication.

The main process is shown in the figure below:

IV communication mode

IPC methods usually include pipes under linux (including nameless pipes and named pipes), message queues, semaphores, signals, shared storage, sockets, Streams, etc. Socket and Streams support two processes IPC on different hosts and Binder on android.

Take C language programming in Linux as an example.

1, Pipeline

Pipe, usually nameless pipe, is the oldest form of IPC in UNIX system.

1. Features:

  1. It is half duplex (that is, data can only flow in one direction) and has fixed read and write ends.

  2. It can only be used for communication between related processes (also between parent-child processes or brother processes).

  3. It can be regarded as a special file, and ordinary read, write and other functions can also be used for its reading and writing. But it is not an ordinary file, does not belong to any other file system, and only exists in memory.

2. Prototype:

#include <unistd.h>
int pipe(int fd[2]);    // Return value: 0 for success and - 1 for failure

When a pipeline is established, call the pipe function to open up a buffer in the kernel for communication. It will create two file descriptors: fd[0] for reading and fd[1] for writing. As shown below:

To close the pipeline, just close the two file descriptors.

3. Examples

Pipelines in a single process are almost useless. Therefore, the process calling pipe usually calls fork, which creates an IPC channel between the parent process and the child process. As shown in the figure below:

To flow data from the parent process to the child process, close the read side of the parent process (fd[0]) and the write side of the child process (fd[1]); On the contrary, the data flow can flow from the child process to the parent process.

#include<stdio.h>
#include<unistd.h>

int main()
{
    int fd[2];  // Two file descriptors
    pid_t pid;
    char buff[20];

    if(pipe(fd) < 0)  // Create pipe
        printf("Create Pipe Error!\n");

    if((pid = fork()) < 0)  // Create child process
    {
        printf("Fork Error!\n");
    }
    else if(pid > 0)  // Parent process
    {
        close(fd[0]); // Close the reader
        write(fd[1], "hello world\n", 12);
    }
    else
    {
        close(fd[1]); // Close write end
        read(fd[0], buff, 20);
        printf("%s", buff);
    }

    return 0;
}

2, FIFO

FIFO, also known as named pipe, is a file type.

1. Characteristics

  1. FIFO can exchange data between unrelated processes, which is different from anonymous pipes.

  2. FIFO has a pathname associated with it. It exists in the file system in the form of a special device file.

2. Prototype

#include <sys/stat.h>
// Success, return value - 1
int mkfifo(const char *pathname, mode_t mode);

The mode parameter is the same as that in the open function. Once a FIFO is created, it can be manipulated with normal file I/O functions.

When a FIFO is open ed, the difference between whether to set the non blocking flag (O_NONBLOCK):

  • If no o is specified_ Nonblock (default), read-only open, to block some other process to open this FIFO for writing. Similarly, writing only open blocks some other process from opening it for reading.

  • If O is specified_ Nonblock, then read-only open returns immediately. If no process has opened the FIFO for reading, its errno is set to ENXIO.

3. Examples

FIFO communication mode is similar to using files to transmit data in the process, but FIFO type files also have the characteristics of pipeline. When the data is read out, the data in the FIFO pipeline is cleared at the same time and "first in first out". The following example demonstrates the process of IPC using FIFO:

write_fifo.c

#include<stdio.h>
#include<stdlib.h>   // exit
#include<fcntl.h>    // O_WRONLY
#include<sys/stat.h>
#include<time.h>     // time

int main()
{
    int fd;
    int n, i;
    char buf[1024];
    time_t tp;

    printf("I am %d process.\n", getpid()); // Description process ID

    if((fd = open("fifo1", O_WRONLY)) < 0) // Open a FIFO with write
    {
        perror("Open FIFO Failed");
        exit(1);
    }

    for(i=0; i<10; ++i)
    {
        time(&tp);  // Take the current time of the system
        n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));
        printf("Send message: %s", buf); // Print
        if(write(fd, buf, n+1) < 0)  // Write to FIFO
        {
            perror("Write FIFO Failed");
            close(fd);
            exit(1);
        }
        sleep(1);  // Sleep for 1 second
    }

    close(fd);  // Close FIFO file
    return 0;
}

read_fifo.c

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>

int main()
{
    int fd;
    int len;
    char buf[1024];

    if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // Create FIFO pipeline
        perror("Create FIFO Failed");

    if((fd = open("fifo1", O_RDONLY)) < 0)  // Open FIFO with read
    {
        perror("Open FIFO Failed");
        exit(1);
    }

    while((len = read(fd, buf, 1024)) > 0) // Read FIFO pipeline
        printf("Read message: %s", buf);

    close(fd);  // Close FIFO file
    return 0;
}

The above example can be extended to an example of client process server process communication, write_fifo is similar to the client. You can open multiple clients and send request information to a server_ FIFO is similar to the server. It monitors the reading end of FIFO in time. When there is data, it reads out and processes it. However, a key problem is that each client must know the FIFO interface provided by the server in advance. The following figure shows this arrangement:

3, Message queue

Message queue is the link table of messages, which is stored in the kernel. A message queue is identified by an identifier (queue ID).

1. Characteristics

  1. Message queues are record oriented, where messages have a specific format and a specific priority.

  2. Message queuing is independent of sending and receiving processes. When the process terminates, the message queue and its contents are not deleted.

  3. Message queue can realize the random query of messages. Messages do not have to be read in the order of first in first out, but also according to the type of message.

2. Prototype

#include <sys/msg.h>
// Create or open a message queue: the queue ID is returned successfully, and - 1 is returned for failure
int msgget(key_t key, int flag);
// Add message: 0 for success and - 1 for failure
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// Read message: the length of message data is returned successfully, and - 1 is returned for failure
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// Control message queue: 0 for success and - 1 for failure
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msgget will create a new message queue in the following two cases:

  • If there is no message queue corresponding to the key value and the flag contains IPC_CREAT flag bit.
  • The key parameter is IPC_PRIVATE.

When the function msgrcv reads the message queue, the type parameter has the following conditions:

  • type == 0, return the first message in the queue;
  • Type > 0, return the first message with message type in the queue;
  • If type < 0, the message whose message type value in the queue is less than or equal to the absolute value of type is returned. If there are multiple messages, the message with the smallest type value is taken.

It can be seen that when the type value is not 0, it is used to read messages in non first in first out order. Type can also be regarded as the weight of priority. (please explain other parameters by yourself)

3. Examples

The following is a simple example of using message queue for IPC. The server program has been waiting for a specific type of message. After receiving this type of message, it sends another specific type of message as feedback, and the client reads the feedback and prints it out.

msg_server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// Used to create a unique key
#define MSG_FILE "/etc/passwd"

// Message structure
struct msg_form {
    long mtype;
    char mtext[256];
};

int main()
{
    int msqid;
    key_t key;
    struct msg_form msg;

    // Get key value
    if((key = ftok(MSG_FILE,'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // Print key value
    printf("Message Queue - Server key is: %d.\n", key);

    // Create message queue
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // Print message queue ID and process ID
    printf("My msqid is: %d.\n", msqid);
    printf("My pid is: %d.\n", getpid());

    // Loop read message
    for(;;)
    {
        msgrcv(msqid, &msg, 256, 888, 0);// Returns the first message of type 888
        printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
        printf("Server: receive msg.mtype is: %d.\n", msg.mtype);

        msg.mtype = 999; // Type of message received by the client
        sprintf(msg.mtext, "hello, I'm server %d", getpid());
        msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
    }
    return 0;
}

msg_client.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// Used to create a unique key
#define MSG_FILE "/etc/passwd"

// Message structure
struct msg_form {
    long mtype;
    char mtext[256];
};

int main()
{
    int msqid;
    key_t key;
    struct msg_form msg;

    // Get key value
    if ((key = ftok(MSG_FILE, 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // Print key value
    printf("Message Queue - Client key is: %d.\n", key);

    // Open message queue
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // Print message queue ID and process ID
    printf("My msqid is: %d.\n", msqid);
    printf("My pid is: %d.\n", getpid());

    // Add message of type 888
    msg.mtype = 888;
    sprintf(msg.mtext, "hello, I'm client %d", getpid());
    msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

    // Read messages of type 999
    msgrcv(msqid, &msg, 256, 999, 0);
    printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
    printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
    return 0;
}

4, Semaphore

semaphore is different from the IPC structure already introduced. It is a counter. Semaphores are used to realize mutual exclusion and synchronization between processes, not to store inter process communication data.

1. Characteristics

  1. Semaphores are used for inter process synchronization. To transfer data between processes, they need to be combined with shared memory.

  2. Semaphores are based on the PV operation of the operating system, and the operation of the program on semaphores is atomic operation.

  3. Each PV operation on the semaphore is not limited to adding or subtracting 1 from the semaphore value, but can add or subtract any positive integer.

  4. Support semaphore group.

2. Prototype

The simplest semaphore is a variable that can only take 0 and 1. This is also the most common form of semaphore, which is called Binary Semaphore. A semaphore that can take multiple positive integers is called a general semaphore.

Semaphore functions under Linux operate on a general semaphore array, rather than on a single binary semaphore.

#include <sys/sem.h>
// Create or obtain a semaphore group: if the semaphore set ID is returned successfully, if it fails, return - 1
int semget(key_t key, int num_sems, int sem_flags);
// Operate the semaphore group and change the value of the semaphore: 0 for success and - 1 for failure
int semop(int semid, struct sembuf semoparray[], size_t numops);
// Relevant information of control semaphore
int semctl(int semid, int sem_num, int cmd, ...);

When semget creates a new semaphore set, the number of semaphores in the set must be specified (i.e. num_sems), usually 1; If you are referencing an existing collection, Num will be used_ SEMS is specified as 0.

In semop function, sembuf structure is defined as follows:

struct sembuf
{
    short sem_num; // Corresponding sequence number in semaphore group, 0 ~ sem_nums-1
    short sem_op;  // The change of semaphore value in one operation
    short sem_flg; // IPC_NOWAIT, SEM_UNDO
}

Where sem_op is the change amount of semaphore in one operation:

  • If sem_op > 0 indicates that the process releases the corresponding number of resources, and SEM_ The value of OP is added to the value of the semaphore. If a process is sleeping waiting for this semaphore, wrap them.

  • If sem_op < 0, request SEM_ The absolute value of Op.

    • If the corresponding number of resources can meet the request, subtract SEM from the value of the semaphore_ The absolute value of op. the function returns successfully.
    • When the corresponding number of resources cannot meet the request, this operation is the same as sem_flg related.
      • sem_flg specify IPC_NOWAIT, the semop function has an error and returns EAGAIN.
      • sem_flg no IPC specified_ Nowait, increase the semncnt value of the semaphore by 1, and then suspend the process until the following conditions occur:
        1. When the corresponding number of resources can meet the request, the semncnt value of this semaphore is subtracted by 1, and the value of this semaphore is subtracted by SEM_ The absolute value of Op. Successful return;
        2. This semaphore is deleted, and the function smeop error is returned to EIDRM;
        3. The process captures the signal and returns it from the signal processing function. In this case, the semncnt value of this semaphore is reduced by 1, and the function semop returns EINTR in case of error
  • If sem_op == 0, the process blocks until the corresponding value of the semaphore is 0:

    • When the semaphore is already 0, the function returns immediately.
    • If the value of the semaphore is not 0, then according to sem_flg determines the function action:
      • sem_flg specify IPC_NOWAIT, an error occurs and EAGAIN is returned.
      • sem_flg no IPC specified_ Nowait, increase the semncnt value of the semaphore by 1, and then suspend the process until the following conditions occur:
        1. If the semaphore value is 0, subtract the semaphore semzcnt value by 1, and the function semop returns successfully;
        2. This semaphore is deleted, and the function smeop error is returned to EIDRM;
        3. The process captures the signal and returns it from the signal processing function. In this case, the semncnt value of this semaphore is reduced by 1, and the function semop returns EINTR in case of error

There are many commands in semctl function. Here are two common commands:

  • SETVAL: used to initialize the semaphore to a known value. The required value is passed as a val member of the union semun. The semaphore needs to be set before the semaphore is used for the first time.
  • IPC_RMID: delete a semaphore set. If you don't delete the semaphore, it will continue to exist in the system. Even if the program has exited, it may cause problems the next time you run the program, and the semaphore is a limited resource.

3. Examples

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

// Consortium for semctl initialization
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// Initialization semaphore
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P operation:
//    If the semaphore value is 1, obtain the resource and set the semaphore value to - 1
//    If the semaphore value is 0, the process hangs and waits
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*Serial number*/
    sbuf.sem_op = -1; /*P operation*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V operation:
//    Release the resource and set the semaphore value to + 1
//    If any processes are pending, wake them up
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*Serial number*/
    sbuf.sem_op = 1;  /*V operation*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// Delete semaphore set
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}


int main()
{
    int sem_id;  // Semaphore set ID
    key_t key;
    pid_t pid;

    // Get key value
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // Create a semaphore set with only one semaphore
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    // Initialization: the initial value is set to 0, and resources are occupied
    init_sem(sem_id, 0);

    if((pid = fork()) == -1)
        perror("Fork Error");
    else if(pid == 0) /*Subprocess*/
    {
        sleep(2);
        printf("Process child: pid=%d\n", getpid());
        sem_v(sem_id);  /*Release resources*/
    }
    else  /*Parent process*/
    {
        sem_p(sem_id);   /*Waiting for resources*/
        printf("Process father: pid=%d\n", getpid());
        sem_v(sem_id);   /*Release resources*/
        del_sem(sem_id); /*Delete semaphore set*/
    }
    return 0;
}

In the above example, if no semaphore is added, the parent process will complete the execution first. A semaphore is added here to let the parent process wait for the child process to execute before executing.

5, Shared memory

Shared Memory refers to two or more processes sharing a given storage area.

1. Characteristics

  1. Direct access to memory is the fastest process of IPC.

  2. Because multiple processes can operate at the same time, synchronization is required.

  3. Semaphores + shared memory are usually used together. Semaphores are used to synchronize access to shared memory.

2. Prototype

#include <sys/shm.h>
// Create or obtain a shared memory: the shared memory ID is returned successfully, and - 1 is returned for failure
int shmget(key_t key, size_t size, int flag);
// Connect the shared memory to the address space of the current process: successfully return the pointer to the shared memory, and fail to return - 1
void *shmat(int shm_id, const void *addr, int flag);
// Disconnect from shared memory: 0 for success and - 1 for failure
int shmdt(void *addr);
// Information about controlling shared memory: 0 for success and - 1 for failure
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

When creating a section of shared memory with shmget function, its size must be specified; If you reference an existing shared memory, specify size as 0.

When a piece of shared memory is created, it cannot be accessed by any process. The shmat function must be used to connect the shared memory to the address space of the current process. After the connection is successful, the shared memory area object is mapped to the address space of the calling process, and then it can be accessed like the local space.

The shmdt function is used to disconnect the connection established by shmat. Note that this does not delete the shared memory from the system, but the current process can no longer access the shared memory.

shmctl function can perform various operations on shared memory, and perform corresponding operations according to parameter cmd. IPC is commonly used_ Rmid (remove the shared memory from the system).

3. Examples

The following example uses the combination of [shared memory + semaphore + message queue] to realize the communication between server process and client process.

  • Shared memory is used to transfer data;
  • Semaphores are used for synchronization;
  • Message queuing is used to notify the server to read after the client modifies the shared memory.

server.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h>  // shared memory
#include<sys/sem.h>  // semaphore
#include<sys/msg.h>  // message queue
#include<string.h>   // memcpy

// Message queue structure
struct msg_form {
    long mtype;
    char mtext;
};

// Consortium for semctl initialization
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// Initialization semaphore
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P operation:
//  If the semaphore value is 1, obtain the resource and set the semaphore value to - 1
//  If the semaphore value is 0, the process hangs and waits
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*Serial number*/
    sbuf.sem_op = -1; /*P operation*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V operation:
//  Release the resource and set the semaphore value to + 1
//  If any processes are pending, wake them up
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*Serial number*/
    sbuf.sem_op = 1;  /*V operation*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// Delete semaphore set
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}

// Create a semaphore set
int creat_sem(key_t key)
{
    int sem_id;
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
        perror("semget error");
        exit(-1);
    }
    init_sem(sem_id, 1);  /*The initial value is set to 1, and the resource is not occupied*/
    return sem_id;
}


int main()
{
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    char data[] = "this is server";
    struct shmid_ds buf1;  /*Used to delete shared memory*/
    struct msqid_ds buf2;  /*Used to delete message queues*/
    struct msg_form msg;  /*Message queuing is used to notify the other party that the shared memory has been updated*/

    // Get key value
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // Create shared memory
    if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
    {
        perror("Create Shared Memory Error");
        exit(1);
    }

    // Connect shared memory
    shm = (char*)shmat(shmid, 0, 0);
    if((int)shm == -1)
    {
        perror("Attach Shared Memory Error");
        exit(1);
    }


    // Create message queue
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // Create semaphore
    semid = creat_sem(key);

    // Read data
    while(1)
    {
        msgrcv(msqid, &msg, 1, 888, 0); /*Read messages of type 888*/
        if(msg.mtext == 'q')  /*quit - Jump out of loop*/
            break;
        if(msg.mtext == 'r')  /*read - Read shared memory*/
        {
            sem_p(semid);
            printf("%s\n",shm);
            sem_v(semid);
        }
    }

    // Disconnect
    shmdt(shm);

    /*Delete shared memory, message queue, semaphore*/
    shmctl(shmid, IPC_RMID, &buf1);
    msgctl(msqid, IPC_RMID, &buf2);
    del_sem(semid);
    return 0;
}

client.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h>  // shared memory
#include<sys/sem.h>  // semaphore
#include<sys/msg.h>  // message queue
#include<string.h>   // memcpy

// Message queue structure
struct msg_form {
    long mtype;
    char mtext;
};

// Consortium for semctl initialization
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// P operation:
//  If the semaphore value is 1, obtain the resource and set the semaphore value to - 1
//  If the semaphore value is 0, the process hangs and waits
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*Serial number*/
    sbuf.sem_op = -1; /*P operation*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V operation:
//  Release the resource and set the semaphore value to + 1
//  If any processes are pending, wake them up
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*Serial number*/
    sbuf.sem_op = 1;  /*V operation*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}


int main()
{
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    struct msg_form msg;
    int flag = 1; /*while Cycle condition*/

    // Get key value
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // Get shared memory
    if((shmid = shmget(key, 1024, 0)) == -1)
    {
        perror("shmget error");
        exit(1);
    }

    // Connect shared memory
    shm = (char*)shmat(shmid, 0, 0);
    if((int)shm == -1)
    {
        perror("Attach Shared Memory Error");
        exit(1);
    }

    // Create message queue
    if ((msqid = msgget(key, 0)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // Get semaphore
    if((semid = semget(key, 0, 0)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    while(flag)
    {
        char c;
        printf("Please input command: ");
        scanf("%c", &c);
        switch(c)
        {
            case 'r':
                printf("Data to send: ");
                sem_p(semid);  /*Access resources*/
                scanf("%s", shm);
                sem_v(semid);  /*Release resources*/
                /*Clear standard input buffer*/
                while((c=getchar())!='\n' && c!=EOF);
                msg.mtype = 888;
                msg.mtext = 'r';  /*Send a message to inform the server to read data*/
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                break;
            case 'q':
                msg.mtype = 888;
                msg.mtext = 'q';
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                flag = 0;
                break;
            default:
                printf("Wrong input!\n");
                /*Clear standard input buffer*/
                while((c=getchar())!='\n' && c!=EOF);
        }
    }

    // Disconnect
    shmdt(shm);

    return 0;
}

Note: when scanf() inputs characters or strings, the buffer is left \ n, so you need to empty the buffer of standard input after each input operation. However, since the gcc compiler does not support fflush(stdin) (it is only an extension of standard C), we use an alternative:

while((c=getchar())!='\n' && c!=EOF);

6, Signal

Signal is an event notification mechanism. When receiving the signal, the process will perform the corresponding operation.

1. Characteristics

  1. Generated by hardware. For example, entering Ctrl+C from the keyboard can terminate the current process
  2. Sent by another process, for example, under the shell process, use the command kill - signal value PID
  3. Exception, send a signal when the process is abnormal

2. Prototype

#include<signal.h>
void(*signal(int sig,void (*func)(int)(int))
// sig: signal value
// func: function pointer of signal processing. The parameter is the signal value

int sigaction(int sig,const struct sigaction *act,struct sigaction *oact);
// sig: signal value
// act: Specifies the action of the signal, equivalent to func
// oact: the action of saving the original signal

int kill(pid_t pid,int sig)
// Its function is to send the signal sig to the pid process and return 0 when successful; There are generally three reasons for failure: the given signal is invalid, the sending permission is insufficient, and the target process does not exist
// The kill call fails and returns - 1. There are usually three reasons for the call failure:
// 1. The given signal is invalid (errno = EINVAL)
// 2. Insufficient sending permission (errno = EPERM)
// 3. The target process does not exist (errno = ESRCH)

The signal is processed by the operating system, so the signal processing is in the kernel state. If it is not an emergency signal, it may not be processed immediately. The operating system will not suspend the currently running process in order to process a signal, because suspending (process switching) the current process consumes a lot. Therefore, the operating system will generally put the signal into the signal table first, and generally choose to process the signal when the kernel state is switched back to the user state (there is no need to switch the process separately to avoid wasting time)

3. Examples

Example of function signal 1 c

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
 
void ouch(int sig)
{
	printf("\nOUCH! - I got signal %d\n", sig);
	//Restore the default behavior of terminal interrupt signal SIGINT
	(void) signal(SIGINT, SIG_DFL);
}
 
int main()
{
	//Change the default behavior of the terminal interrupt signal SIGINT to execute the out function
	//Instead of terminating the execution of the program
	(void) signal(SIGINT, ouch);
	while(1)
	{
		printf("Hello World!\n");
		sleep(1);
	}
	return 0;
}

Signal2. Example of function signaling function c

#include <unistd.h>
#include <stdio.h>
#include <signal.h>
 
void ouch(int sig)
{
	printf("\nOUCH! - I got signal %d\n", sig);
}
 
int main()
{
	struct sigaction act;
	act.sa_handler = ouch;
	//Create an empty signal mask word, that is, do not mask any information
	sigemptyset(&act.sa_mask);
	//Reset sigaction function to default behavior
	act.sa_flags = SA_RESETHAND;
 
	sigaction(SIGINT, &act, 0);
 
	while(1)
	{
		printf("Hello World!\n");
		sleep(1);
	}
	return 0;
}

A comprehensive example signal3 c

int main()
{
	pid_t pid;
	pid=fork();
	switch(pid)
	{
	case -1:
		perror("fork failed\n");
 
	case 0://Subprocess
		sleep(5);
		kill(getppid(),SIGALRM);
		exit(0);
	default:;
	}
 
	signal(SIGALRM,func);
	while(!n)
	{
		printf("hello world\n");
		sleep(1);
	}
	if(n)
	{
		printf("hava a signal %d\n",SIGALRM);
	}
	exit(0);
}

VII Socket

1, What is socket

Socket, that is, socket, is a communication mechanism. With this mechanism, the development of client / server (i.e. the process to communicate) system can be carried out not only on the local single machine, but also across the network. That is, it allows processes that are not on the same computer but are connected to the computer through a network to communicate. Because of this, sockets clearly distinguish between clients and servers.

2, Properties of socket

The characteristics of sockets are determined by three attributes: domain, type and protocol.

1. Domain socket

It specifies the network medium used in socket communication. The most common socket domain is AF_INET, which refers to the Internet network. When the client uses socket to connect across the network, it needs to use the IP address and port of the server computer to specify a specific service on a networked machine. Therefore, when using socket as the end of communication, the server application must bind a port before starting communication, and the server waits for the client's connection at the specified port. Another domain AF_UNIX stands for UNIX file system, which is file input / output, and its address is file name.

2. Socket type

The Internet provides two communication mechanisms: stream and datagram. Therefore, the types of sockets are divided into stream socket and datagram socket. This is mainly about streaming sockets.

Stream sockets are of type SOCK_STREAM specifies that they are in AF_ In INET domain, it is realized through TCP/IP connection, and it is also AF_ Socket type commonly used in UNIX. Stream socket provides an orderly, reliable and bidirectional byte stream connection, so the sent data can ensure that it will not be lost, repeated or arrive out of order, and it also has a certain mechanism of resending after an error.

As opposed to a stream socket, the type SOCK_DGRAM specifies the datagram socket. It does not need to establish a connection and maintain a connection. They are in AF_INET is usually implemented through UDP/IP protocol. It limits the length of data that can be sent. Datagram is transmitted as a separate network message, which may be lost, copied or arrive disorderly. UDP is not a reliable protocol, but its speed is relatively high, because it does not always need to establish and maintain a connection.

3. Socket protocol

As long as the underlying transport mechanism allows more than one protocol to provide the required socket type, we can choose a specific protocol for the socket. Typically, only default values need to be used.

3, Socket address

Socket interface is represented by socket data structure in the following form:

struct socket
{
  socket_state  state;     /* Indicates the connection status of a socket interface. The connection status of a socket interface can be as follows:
The socket interface is idle and has not been bound with the corresponding port and address; Not connected yet; Connecting; Connected; Disconnecting.     */
  unsigned long     flags;
  struct proto_ops  ops;  /* Indicates the various operations that can be performed on the socket */
  struct inode      inode; /* Point to the corresponding inode in the sockfs file system */
  struct fasync_struct  *fasync_list; /* Asynchronous wake up list  */
  struct file       *file;            /* Point to the corresponding file in the sockfs file system  */
  struct sock       sk;  /* Any protocol family has its specific socket characteristics, and this field points to the socket object of a specific protocol family. */
  wait_queue_head_t  wait;
  short              type;
  unsigned char      passcred;
};

Each socket has its own address format for AF_ For UNIX domain sockets, its address is determined by the structure SOCKADDR_ The structure is defined in the header file sys / UN In H, it is defined as follows:

struct sockaddr_un{
    sa_family_t sun_family;//AF_UNIX, which is a short integer
    char        sum_path[];//Pathname
};

For AF_ For INET domain sockets, its address structure is composed of sockaddr_in, which includes at least the following members:

struct sockaddr_in{
    short int            sin_family;//AF_INET
    unsigned short int    sin_port;//Port number
    struct in_addr        sin_addr;//IP address
};

And in_addr is defined as:

struct in_addr{
    unsigned long int s_addr;
};

4, Workflow of client / server based on stream socket

How does the client / server system used by the process communicating with socket work?

1. Server side

First, the server application uses the system call socket to create a socket. It is a resource similar to the file descriptor allocated by the system to the server process, which cannot be shared with other processes.

Next, the server process will give the socket a name. We use the system call bind to name the socket. The server process then starts waiting for the client to connect to the socket.

Then, the system calls listen to create a queue and use it to store incoming connections from customers.

Finally, the server accepts the client's connection through the system call accept. It creates a new socket different from the original named socket. This socket is only used to communicate with this specific client, while the named socket (i.e. the original socket) is retained to continue to process connections from other clients.

2. Client

The socket based client is simpler than the server. Similarly, the client application first calls the socket to create an unnamed socket, and then uses the server's named socket as an address to call connect to establish a connection with the server.

Once the connection is established, we can use sockets to realize two-way data communication like using the underlying file descriptor.    

5, Interface and function of streaming socket

The interface function of socket is declared in the header file sys / types H and sys / socket H medium.

1. Create socket -- socket system call

This function is used to create a socket and return a descriptor, which can be used to access the socket. Its prototype is as follows:

int socket(int domain, int type, int protocol);

The three parameters in the function correspond to the three socket attributes mentioned above. Setting the protocol parameter to 0 means that the default protocol is used.

2. Named (bound) socket -- bind system call

This function names the socket created through the socket call, so that it can be used by other processes. For AF_UNIX, after calling this function, the socket will be associated with a file system pathname. For AF_INET, it will be associated with an IP port number. The function prototype is as follows:

int bind( int socket, const struct sockaddr *address, size_t address_len);

It returns 0 on success and - 1 on failure;

3. Creating SOCKET queue (listening) -- listen system call

This function is used to create a queue to hold unprocessed requests. It returns 0 on success and - 1 on failure. Its prototype is as follows:

int listen(int socket, int backlog);

backlog is used to specify the length of the queue. The maximum number of incoming connections waiting to be processed cannot exceed this number, otherwise future connections will be rejected, resulting in the failure of the customer's connection request. After calling, the program will always listen to the IP port. If there is a connection request, it will be added to the queue.

4. Accept connection - accept system call

The system call is used to wait for the client to establish a connection to the socket. The accept system call returns only when the client program attempts to connect to the socket specified by the socket parameter, that is, if there is no unprocessed connection in the socket queue, accept will block until a client establishes a connection. The accept function will create a new socket to communicate with the client and return the descriptor of the new socket. The type of the new socket is the same as that of the server listening socket. Its prototype is as follows:

int accept(int socket, struct sockaddr *address, size_t *address_len);

Address is the address of the connecting client, and the parameter address_len specifies the length of the customer structure. If the length of the customer address exceeds this value, it will be truncated.

5. Request connection - connect system call

The system call is used to let the client program connect to the server by establishing a connection between an unnamed socket and the server listening socket. Its prototype is as follows:

int connect(int socket, const struct sockaddr *address, size_t address_len);

The socket specified by parameter socket connects to the server socket specified by parameter addres. It returns 0 on success and - 1 on failure

6. Close socket -- close system call

The socket should always be used to close the connection between the client and the server.

6, Processes communicate using streaming socket s

The following uses multiple client programs and a server program to show how processes communicate using sockets.

sockserver.c is a server program. It first creates a socket, then binds a port, then listens to the socket and ignores the stop message of the sub process. Then it enters the loop and keeps checking whether there is a client connected to the server. If so, it calls fork to create a sub process to process the request. The read system call is used to read the information sent by the client, and the write system call is used to send information to the client. The work of this server is very simple, that is, the character sent by the customer + 1, and then send it back to the customer.

sockclient.c is a client program. It also needs to create a socket connection first, and then connect to the specified IP port server. If the connection is successful, use write to send information to the server, and then read to obtain the information processed by the server, and then output it.

Server sockserver The source code of C is as follows:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int server_sockfd = -1;
    int client_sockfd = -1;
    int client_len = 0;
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    //Create stream socket
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    //Set the connection address received by the server and the listening port
    server_addr.sin_family = AF_INET;//Specify network socket
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//Accept connections from all IP addresses
    server_addr.sin_port = htons(9736);//Bind to port 9736
    //Bind (named) socket
    bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    //Create SOCKET queue and listen for sockets
    listen(server_sockfd, 5);
    //Ignore child process stop or exit signals
    signal(SIGCHLD, SIG_IGN);
    
    while(1)
    {
        char ch = '\0';
        client_len = sizeof(client_addr);
        printf("Server waiting\n");
        //Accept the connection and create a new socket
        client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len);
 
        if(fork() == 0)
        {
            //In the sub process, read the information sent by the client, process the information, and then send it to the client
            read(client_sockfd, &ch, 1);
            sleep(5);
            ch++;
            write(client_sockfd, &ch, 1);
            close(client_sockfd);
            exit(0);
        }
        else
        {
            //In the parent process, close the socket
            close(client_sockfd);
        }
    }
}

Customer sockclient The source code of C is as follows:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int sockfd = -1;
    int len = 0;
    struct sockaddr_in address;
    int result;
    char ch = 'A';
    //Create stream socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    //Set the information of the server to connect to
    address.sin_family = AF_INET;//Using network sockets
    address.sin_addr.s_addr = inet_addr("127.0.0.1");//server address
    address.sin_port = htons(9736);//The port on which the server listens
    len = sizeof(address);
    //Connect to server
    result = connect(sockfd, (struct sockaddr*)&address, len);
 
    if(result == -1)
    {
        perror("ops:client\n");
        exit(1);
    }
    //Send request to server
    write(sockfd, &ch, 1);
    //Get data from server
    read(sockfd, &ch, 1);
    printf("char form server = %c\n", ch);
    close(sockfd);
    exit(0);
}

VIII android binder

Binder is defined differently in different scenarios

I Binder cross process communication mechanism model

1 model schematic diagram

Binder cross process communication mechanism model is based on Client - Server mode


2 model composition and role description


3 function & principle of binder drive

brief introduction


Core principles of cross process communication

4. Description of model principle steps


Additional instructions
Note 1: the interaction between Client process, Server process and service manager process must be driven by Binder (using open and ioctl file operation functions), rather than direct interaction
reason:

  1. Client process, Server process & service manager process belong to the user space of process space, and inter process interaction is not allowed
  2. Binder driver belongs to the kernel space of process space, which can carry out inter process & intra process interaction

Therefore, the schematic diagram can be expressed as follows:

Dashed lines indicate that there is no direct interaction

Note 2: Binder driver & service manager process belongs to Android infrastructure (i.e. the system has been implemented); The Client process and Server process belong to the Android application layer (which needs to be implemented by the developer)
Therefore, in cross process communication, developers only need to customize the client & server process and explicitly use the above three steps, and finally complete the inter process communication with the help of the basic architecture function of Android

Description 3: thread management of Binder request
The Server process creates many threads to process Binder requests
The thread management of Binder model adopts Binder driven thread pool and is managed by Binder driver itself
Not managed by the Server process

The default maximum number of Binder threads in a process is 16. Requests exceeding this number will be blocked and wait for idle Binder threads.
Therefore, when dealing with concurrency during inter process communication, such as using ContentProvider, its CRUD (create, retrieve, update and delete) method can only have 16 threads working at the same time

So far, I believe you have a very clear qualitative understanding of Binder's cross process communication mechanism model
Next, I will analyze the specific code implementation of Binder cross process communication mechanism model in Android through an example
That is, analyze how the above steps are implemented in code in Android

5. Specific implementation principle of binder mechanism in Android

The implementation of Binder mechanism in Android mainly depends on Binder class, which implements IBinder interface
Step 1: register the service
Process description
The Server process registers services with the Service Manager process through the Binder driver
code implementation

  1. The Server process creates a Binder object
  2. Binder entity is the existing form of Server process in binder driver
  3. This object holds information about the Server and ServiceManager (stored in kernel space)
  4. The Binder driver finds the Server object in user space through the Binder entity in kernel space

Code analysis

    
    Binder binder = new Stub();
    // Step 1: create Binder object - > > analysis 1

    // Step 2: create an anonymous class of IInterface interface class
    // Before creation, you need to pre define the interface that inherits the IInterface interface -- > analysis 3
    IInterface plus = new IPlus(){

          // Determine the method that the Client process needs to call
          public int add(int a,int b) {
               return a+b;
         }

          // Implement the only method in IInterface interface
          public IBinder asBinder(){ 
                return null ;
           }
};
          // Step 3
          binder.attachInterface(plus,"add two int");
         // 1. Store (add two, int, plus) as a (key,value) pair into a map < string, iiinterface > object in the Binder object
         // 2. After that, the Binder object can obtain the reference of the corresponding IInterface object (i.e. plus) through querylocalinterface() according to add two int, and can rely on this reference to complete the call to the request method
        // After analysis, jump out


<-- Analysis 1: Stub class -->
    public class Stub extends Binder {
    // Inherited from Binder class - > > analysis 2

          // Replication onTransact()
          @Override
          boolean onTransact(int code, Parcel data, Parcel reply, int flags){
          // Wait until step 3 to explain the specific logic. Skip here first
          switch (code) { 
                case Stub.add:  { 

                       data.enforceInterface("add two int"); 

                       int  arg0  = data.readInt();
                       int  arg1  = data.readInt();

                       int  result = this.queryLocalIInterface("add two int") .add( arg0,  arg1); 

                        reply.writeInt(result); 

                        return true; 
                  }
           } 
      return super.onTransact(code, data, reply, flags); 

}
// Go back to step 1 above and continue with step 2

<-- Analysis 2: Binder class -->
 public class Binder implement IBinder{
    // The implementation of Binder mechanism in Android mainly depends on Binder class, which implements IBinder interface
    // IBinder interface: it defines the basic interface of remote operation object and represents the ability of cross process transmission
    // The system will provide cross process transmission capability for each object that implements IBinder interface
    // That is, Binder class objects have the ability of cross process transmission

        void attachInterface(IInterface plus, String descriptor);
        // effect:
          // 1. Store (descriptor, plus) as a (key,value) pair into a map < string, iinterface > object in the Binder object
          // 2. After that, the Binder object can obtain the reference of the corresponding IInterface object (i.e. plus) through querylocalinterface() according to the descriptor, and can rely on this reference to complete the call to the request method

        IInterface queryLocalInterface(Stringdescriptor) ;
        // Function: find the corresponding IInterface object (i.e. plus reference) according to the parameter descriptor

        boolean onTransact(int code, Parcel data, Parcel reply, int flags);
        // Definition: inherited from IBinder interface
        // Function: execute the target method requested by the Client process (subclass needs replication)
        // Parameter Description:
        // code: Client process request method identifier. That is, the Server process determines the requested target method according to the identification
        // data: parameters of the target method. (passed in by the Client process, here are integers a and b)
        // reply: the result after the target method is executed (returned to the Client process)
         // Note: run in Binder thread pool of Server process; When the Client process initiates a remote request, the remote request will require the system bottom layer to execute the callback method

        final class BinderProxy implements IBinder {
         // That is, the proxy object class of the Binder object created by the Server process
         // This class belongs to Binder's internal class
        }
        // Back to analysis 1
}

<-- Analysis 3: IInterface Interface implementation class -->

 public interface IPlus extends IInterface {
          // Inherited from IInterface interface - > > analysis 4
          // Define the interface method to be implemented, that is, the method to be called by the Client process
         public int add(int a,int b);
// Return to step 2
}

<-- Analysis 4: IInterface Interface class -->
// Common interface defined by interprocess communication
// Cross process communication can be realized by defining the interface, and then implementing the interface on the server and calling the interface on the client.
public interface IInterface
{
    // There is only one method: return the Binder object associated with the current interface.
    public IBinder asBinder();
}
  // Go back to analysis 3

After registering the service, the Binder driver holds the Binder entity created by the Server process

Step 2: get service

  • Before the Client process uses a service (here is the add function), it must obtain the corresponding service information from the service manager process through the Binder driver
  • The specific code implementation process is as follows

 

At this point, the Client process and the Server process have established a connection

Step 3: using services
According to the obtained Service information (Binder proxy object), the Client process establishes a communication link with the Server process where the Service is located through the Binder driver, and starts using the Service

Process description

  1. The Client process sends parameters (integers a and b) to the Server process
  2. The Server process calls the target method (i.e. addition function) according to the requirements of the Client process
  3. The Server process returns the result of the target method (i.e. the added result) to the Client process

Code implementation process

Step 1: the Client process sends the parameters (integers a and b) to the Server process

// 1. The client process writes the data to be transferred into the Parcel object
// Data = data = parameters of the target method (passed in by the Client process, here are integers a and b) + identifier descriptor of IInterface interface object
  android.os.Parcel data = android.os.Parcel.obtain();
  data.writeInt(a); 
  data.writeInt(b); 

  data.writeInterfaceToken("add two int");;
  // The method object identifier allows the Server process to find the corresponding IInterface object (i.e. the plus created by the Server) through querylocalinterface() according to "add two int" in the Binder object. The addition method that the Client process needs to call is in this object

  android.os.Parcel reply = android.os.Parcel.obtain();
  // reply: the result after the target method is executed (here is the result after addition)

// 2. Send the above data to the Binder driver by calling transact() of the proxy object
  binderproxy.transact(Stub.add, data, reply, 0)
  // Parameter Description:
    // 1. Stub.add: identifier of the target method (agreed by the Client process and the Server process, which can be any)
    // 2. data: the above Parcel object
    // 3. reply: returns the result
    // 0: it doesn't matter

// Note: after sending data, the thread of the Client process will be suspended temporarily
// Therefore, if the Server process performs time-consuming operations, please do not use the main thread to prevent ANR


// 3. Binder driver finds the Server process of the corresponding real binder object according to the proxy object (automatically executed by the system)
// 4. Binder driver sends the data to the Server process and informs the Server process to unpack (automatically executed by the system)

Step 2: the Server process calls the target method (i.e. addition function) according to the requirements of the Client

// 1. After receiving the Binder driver notification, the Server process unpacks the data by calling the Binder object onTransact() & calling the target method
  public class Stub extends Binder {

          // Replication onTransact()
          @Override
          boolean onTransact(int code, Parcel data, Parcel reply, int flags){
          // code is the identifier of the target method agreed in transact()

          switch (code) { 
                case Stub.add:  { 
                  // a. Unpack data in Parcel
                       data.enforceInterface("add two int"); 
                        // a1.  Resolve the identifier of the target method object

                       int  arg0  = data.readInt();
                       int  arg1  = data.readInt();
                       // a2.  Get the parameters of the target method
                      
                       // b. According to "add two int", obtain the reference of the corresponding IInterface object (i.e. the plug created by the Server) through querylocalinterface(), and call the method through the object reference
                       int  result = this.queryLocalIInterface("add two int") .add( arg0,  arg1); 
                      
                        // c. Write calculation results to reply
                        reply.writeInt(result); 
                        
                        return true; 
                  }
           } 
      return super.onTransact(code, data, reply, flags); 
      // 2. Return the settlement result to Binder driver

Step 3: the Server process returns the result of the target method (i.e. the added result) to the Client process

  // 1. The binder driver returns the result along the original path according to the proxy object and notifies the Client process to obtain the returned result
  // 2. Receive the result through the proxy object (the previously suspended thread is awakened)

    binderproxy.transact(Stub.ADD, data, reply, 0);
    reply.readException();;
    result = reply.readInt();
          }
}

Summarize step 3 with a flowchart

Vi Code case

1. binder_common.h

#ifndef __BINDER_COMMON_H__
#define __BINDER_COMMON_H__

#include <opencv2/opencv.hpp>

using namespace cv;

#define DBG_TAG "XlabBinderServer"
/* Common debugging print information with function name and line number */
#define DBG_PRINT(fmt, args...) \
    do { \
        printf(DBG_TAG "[%s:%d] --- " fmt "\n",__FUNCTION__,__LINE__, ##args); \
    } while (0)

typedef struct {
    int size;
    int rows;
    int cols;
} XlabCome_S;

typedef enum {
    ENUM_INIT = 0,
} BinderFunction_E;

typedef enum {
    ENUM_AK_READ = 0,
    ENUM_AK_WRITE,
} BinderRdwrflag_E;

class XlabBindeCommon {
public:
    static uint32_t saveToFile(void *data, uint32_t size, char *path) {
        FILE *fp = fopen((const char *)path, "w+");
        if (fp == NULL) {
            DBG_PRINT("open failed!\n");
            return 0;
        }
        uint32_t ret = fwrite(data, 1, size, fp);
        fclose(fp);

        return ret;
    };

    static  uint32_t readFromFile(void *data, uint32_t size, const char *path)
    {
        FILE *fp = fopen((const char *)path, "rb");
        if (fp == NULL) {
            return 0;
        }
        uint32_t ret = fread(data, 1, size, fp);
        fclose(fp);

        return ret;
    };

    /**
    * Set shared memory file
    * @return     Returns a pointer to shared memory
    * @param      XlabCome_S Structure rdwrflag read / write flag bit
    * @exception    nothing
    **/
    static uchar * sharedMemoryMap(XlabCome_S *xlabcom,BinderRdwrflag_E rdwrflage) {
        int fd = open("/mnt/xlab_mmap", O_RDWR | O_CREAT, 00777);
        if (fd < 0) {
            DBG_PRINT("open mmap filed!\n");
            return nullptr;
        }

        if (ENUM_AK_WRITE) {
            ftruncate(fd, xlabcom->size);     //Change file size
        }
        unsigned char *mapped = static_cast<unsigned char *>(mmap(NULL, xlabcom->size,
                                PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
        if (mapped == MAP_FAILED) {
            DBG_PRINT("mmap failed\n");
            return nullptr;
        }
        return mapped;
    };
};

#endif

client side

binder_client.h

#ifndef __AK_BINDER_CLIENT_H__
#define __AK_BINDER_CLIENT_H__
#include <binder/IBinder.h>
#include <binder/Binder.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include "binder_common.h"

using namespace android;

class XlabBinder {
public:
    XlabBinder() {
        m_binder = NULL;
    }

    ~XlabBinder() {};

    /**
    * Initialize shared memory
    * @return     
    * @param      stp_para 
    * @exception    nothing
    **/
    int Init(unsigned char *readBuf, XlabCome_S *xlabcom);

private:
    sp <IBinder> m_binder;
    //Get service interface through ServiceManager
    bool getXlabBinderService();
};
//namespace

#endif

binder_client.cpp

#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fstream>
#include "binder_client.h"

using namespace android;

int XlabBinder::Init(unsigned char *readBuf, XlabCome_S *xlabcom)
{
    uchar *readBuf1 = XlabBindeCommon::sharedMemoryMap(xlabcom, ENUM_AK_WRITE);
    if (readBuf1 == nullptr) {
        DBG_PRINT("memory map open filed!\n");
        return -2;
    }
    memcpy(readBuf1,readBuf,xlabcom->size);
    bool ret = getXlabBinderService();
    if (ret == true) {
        Parcel data, reply;
        status_t ret = data.write((void *)xlabcom, sizeof(XlabCome_S));
        if (ret != NO_ERROR) {
            DBG_PRINT("trans failed!!");
        }
        m_binder->transact(ENUM_INIT, data, &reply);
        bool answer = reply.readByte();

        DBG_PRINT("answer %d \n", answer);
        return answer;
    } else {
        DBG_PRINT("connect server errorr\n");
        return -5;
    }
}


bool XlabBinder::getXlabBinderService()
{
    ProcessState::initWithDriver("/dev/vndbinder");
    sp <IServiceManager> sm = defaultServiceManager();
    bool ret = false;
    int retry = 200;       // Start and wait for the Service to start
    if (m_binder == NULL) {
        while (retry-- > 0) {
            m_binder = sm->getService(String16("android.xlabservice"));
            if (m_binder == NULL) {
                DBG_PRINT("xlabservice not published, waiting...\n");
                usleep(100 * 1000);
                continue;
            } else {
                ret = true;
                break;
            }
        }
    } else {
        ret = true;
    }
    return ret;
}


int main()
{
    XlabBinder xlb;
    Mat src_img = imread("Test.png");
    int size = src_img.cols * src_img.rows *3;
	XlabCome_S xlabcome;
    xlabcome.size = size;
    xlabcome.cols = src_img.cols;
    xlabcome.rows = src_img.rows;
    xlb.Init(src_img.data, &xlabcome);
  
    return 0;
}

server side

binder_server.cpp

#include <sys/types.h>
#include <unistd.h>
#include <grp.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include <utils/Log.h>
#include <private/android_filesystem_config.h>
#include "binder_service.h"

using namespace android;
int main()
{
    ProcessState::initWithDriver("/dev/vndbinder");
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();// Get ServiceManager interface
    XlabBinderService::instantiate();
    // Execute the addService() function to register the service
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
    // Enter the loop and wait for the client's request
    return 0;
}

binder_service.h

#ifndef __BINDER_SERVER_H__
#define __BINDER_SERVER_H__

#include <utils/threads.h>
#include <utils/RefBase.h>
#include <binder/IInterface.h>
#include <binder/BpBinder.h>
#include <binder/Parcel.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "binder_common.h"


namespace android {

class XlabBinderService : public BBinder {
public:
    static int instantiate();
    XlabBinderService();
    virtual ~XlabBinderService();
    virtual status_t onTransact(uint32_t, const Parcel &, Parcel *, uint32_t);
private:

};

}; //namespace

#endif

binder_service.cpp

#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <unistd.h>
#include "binder_service.h"
#include "binder_common.h"

namespace android {
XlabBinderService::XlabBinderService()
{}

XlabBinderService::~XlabBinderService()
{}

int XlabBinderService::instantiate()
{
    // Call addService() to register a new service with ServiceManager
    int r = defaultServiceManager()->addService(String16("android.xlabservice"),
                    new XlabBinderService());
    DBG_PRINT("xlabservice start!\n");
    return r;
}

// This function analyzes the received data packet and calls the corresponding interface function to process the request
status_t XlabBinderService::onTransact(uint32_t code, const Parcel &data,
        Parcel *reply, uint32_t flags)
{
    DBG_PRINT("code is : %d\n", code);
    int rets = 0;
    switch (code) {
        case ENUM_INIT: {
            XlabCome_S *xlabcom = (XlabCome_S *)data.readInplace(sizeof(XlabCome_S));
            if(xlabcom == NULL){
                DBG_PRINT("recv data init error..");
                rets = -1;
            }

            unsigned char *readBuf = XlabBindeCommon::sharedMemoryMap(xlabcom,ENUM_AK_READ);
            if (readBuf == nullptr) {
                DBG_PRINT("memory map open filed!\n");
                rets = -2;
            }
            if (0 == access("/sdcard/Binder", 0)) {
                Mat dst(xlabcom->rows, xlabcom->cols , CV_8UC3, readBuf);
                imwrite("/sdcard/Binder/InitMapTest.png",dst);
            }

            DBG_PRINT("ret = %d\n", rets);
            reply->writeByte(rets);
            break;
        }
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
    return rets;
}
}; //namespace

 

Topics: C++ Linux