v77.01 Hongmeng kernel source code analysis (message encapsulation) 𞓜 analyze the communication content of LiteIpc process | I wish you a lively and powerful new year

One hundred blog Analysis | this article is: (message encapsulation) | analyze the communication content of LiteIpc process

Related articles of process communication are:

Basic concepts

LiteIPC is a new IPC (inter process communication) mechanism provided by OpenHarmony LiteOS-A kernel. It is a lightweight inter process communication component and provides inter process communication capability for service-oriented system service framework. It is divided into two parts: Kernel Implementation and user mode implementation. The kernel implementation completes inter process messaging, IPC memory management Timeout notification, death notification and other functions; User mode provides serialization and deserialization capabilities, and completes the distribution of IPC callback message and death message.

We mainly explain the kernel state implementation part. I wanted to finish one article, but I found that it is much more complex and important than expected. Therefore, we divided it into two parts: communication content and communication mechanism. The content of communication is the message. There are more than 10 structures around the message. If we can't figure out the relationship between them, we must not understand the communication mechanism, so we have to figure out the relationship first and then the process. The following figure is the message encapsulation diagram drawn by the author after reading the LiteIPC module. It can be said that LiteIPC is the module with the most structures involved in the kernel. Please digest and understand. This article will focus on it.

As mentioned many times in the series, each module of the kernel is expanded around at least one important structure. If you grasp it, you can wipe the details clearly. In LiteIPC, this structure is IpcMsg.

operating mechanism

typedef struct {//IPC message structure
    MsgType        type;       	/**< cmd type, decide the data structure below | The command type determines the following data structure*/
    SvcIdentity    target;    	/**< serviceHandle or targetTaskId, depending on type | Varies by command type*/
    UINT32         code;      	/**< service function code | Service function code*/
    UINT32         flag;		///< label
#if (USE_TIMESTAMP == 1)
    UINT64         timestamp;	///< timestamp for verification
#endif
    UINT32         dataSz;    	/**< size of data | Message content size*/
    VOID           *data;		///< message content, the message to be delivered. This data content refers to the content of spObjNum data. The positioning depends on offsets
    UINT32         spObjNum;	///< number of objects, for example, when spObjNum = 3, offsets = [0,35,79], which means reading 0 - 35 from data to the first object, and so on
    VOID           *offsets;	///< offset. Note that there will be as many offsets as there are spObjNum. See CopyDataFromUser for details
    UINT32         processID; 	/**< filled by kernel, processId of sender/reciever | The process ID provided by the kernel to send / receive messages*/
    UINT32         taskID;    	/**< filled by kernel, taskId of sender/reciever | The task ID provided by the kernel to send / receive messages*/
#ifdef LOSCFG_SECURITY_CAPABILITY	
    UINT32         userID;		///< user ID
    UINT32         gid;			///< group ID
#endif
} IpcMsg;

unscramble

  • First, the essence of communication is that you come and go. Of course, exceptions should also be considered
      typedef enum {	
          MT_REQUEST,	///< request
          MT_REPLY,	///< reply
          MT_FAILED_REPLY,///< reply failed
          MT_DEATH_NOTIFY,///< notice of death
          MT_NUM
      } MsgType;
    
  • The second target, LiteIPC, has two main concepts: ServiceManager and Service. The whole system can only have one ServiceManager, while the Service can have multiple. ServiceManager has two main functions: one is responsible for the registration and logoff of services, and the other is responsible for managing the access rights of services (only authorized tasks can send IPC messages to the corresponding services). First, register the Task that needs to receive IPC messages as a Service through ServiceManager, and then configure access permissions for the Service Task through ServiceManager, that is, specify which tasks can send IPC messages to the Service Task. The core idea of LiteIPC is to maintain an IPC message queue for each Service Task in the kernel state. The message queue provides the upper user state program with read operations on behalf of receiving IPC messages and write operations on behalf of sending IPC messages through LiteIPC device files.
    ///SVC(service) service ID card 
      typedef struct {
          UINT32         handle;  //service ID, range [0, maximum task ID]
          UINT32         token;	//Brought in by the application layer
          UINT32         cookie;	//Brought in by the application layer
      } SvcIdentity;
    
  • code and timestamp are set by the application layer to ensure that the reply is correct and valid. See checkreceivedmsg for details
  • dataSz, data, spObjNum and offsets need to be understood together, which is the top priority. In fact, messages are divided into three types (objects)
      typedef enum {
          OBJ_FD,		///Handle file
          OBJ_PTR,	///< pointer
          OBJ_SVC		///< service to set permissions
      } ObjType;
      typedef union {
          UINT32      fd; 	///< file descriptor
          BuffPtr     ptr;	///< start address of cache, i.e. pointer. When messages come from user space, the content shall be copied to kernel space
          SvcIdentity  svc;	///< service to set access rights
      } ObjContent;
      typedef struct { // Ipcmsg - > data contains three sub messages, which should also be read into kernel space
          ObjType     type; ///< type
          ObjContent  content;///< content
      } SpecialObj;
    
    These three objects are packaged in data. The total length is dataSz, spObjNum represents the number, and offsets is an integer array that marks the position of the corresponding object in data, so it is easy to read the data of the object from data. UINT32 fd type object communication is realized by sharing the same fd between two processes. The specific implementation function is HandleFd.
    ///Handled as a handle, the parameter processID is often not the current process
      LITE_OS_SEC_TEXT STATIC UINT32 HandleFd(UINT32 processID, SpecialObj *obj, BOOL isRollback)
      {
          int ret;
          if (isRollback == FALSE) { // No rollback
              ret = CopyFdToProc(obj->content.fd, processID);//The purpose is to point two different processes fd to the same system fd and share the feeling of fd
              if (ret < 0) {//Returns the new fd of the processID
                  return ret;
              }
              obj->content.fd = ret; // Record the new FD of processID, which can be used for rollback
          } else {// Close process FD on rollback
              ret = CloseProcFd(obj->content.fd, processID);
              if (ret < 0) {
                  return ret;
              }
          }
    
    SvcIdentity svc is used to set mutual access permissions between processes < - > tasks. The specific implementation function is HandleSvc.
    ///It is processed as a service. It is inferred here that Svc should be the abbreviation of service @ note_thinking
      LITE_OS_SEC_TEXT STATIC UINT32 HandleSvc(UINT32 dstTid, const SpecialObj *obj, BOOL isRollback)
      {
          UINT32 taskID = 0;
          if (isRollback == FALSE) {
              if (IsTaskAlive(obj->content.svc.handle) == FALSE) {
                  PRINT_ERR("Liteipc HandleSvc wrong svctid\n");
                  return -EINVAL;
              }
              if (HasServiceAccess(obj->content.svc.handle) == FALSE) {
                  PRINT_ERR("Liteipc %s, %d\n", __FUNCTION__, __LINE__);
                  return -EACCES;
              }
              if (GetTid(obj->content.svc.handle, &taskID) == 0) {//Get the task to which the parameter message service ID belongs
                  if (taskID == OS_PCB_FROM_PID(OS_TCB_FROM_TID(taskID)->processID)->ipcInfo->ipcTaskID) {//If the task ID is the same, that is, the task ID is ServiceManager
                      AddServiceAccess(dstTid, obj->content.svc.handle);
                  }
              }
          }
          return LOS_OK;
      }
    
    BuffPtr ptr transfers values through pointers. The specific implementation function is HandlePtr, and the corresponding structure is BuffPtr.
      typedef struct {
          UINT32         buffSz;  ///< size
          VOID           *buff;	///< content kernel needs to copy content from user space to kernel space 
      } BuffPtr;
    ///Handle in pointer mode
      LITE_OS_SEC_TEXT STATIC UINT32 HandlePtr(UINT32 processID, SpecialObj *obj, BOOL isRollback)
      {
          VOID *buf = NULL;
          UINT32 ret;
          if ((obj->content.ptr.buff == NULL) || (obj->content.ptr.buffSz == 0)) {
              return -EINVAL;
          }
          if (isRollback == FALSE) {
              if (LOS_IsUserAddress((vaddr_t)(UINTPTR)(obj->content.ptr.buff)) == FALSE) { // Determine whether it is a user space address
                  PRINT_ERR("Liteipc Bad ptr address\n"); //When not in user space
                  return -EINVAL;
              }
              buf = LiteIpcNodeAlloc(processID, obj->content.ptr.buffSz);//Allocate memory in kernel space to receive data from user space
              if (buf == NULL) {
                  PRINT_ERR("Liteipc DealPtr alloc mem failed\n");
                  return -EINVAL;
              }
              ret = copy_from_user(buf, obj->content.ptr.buff, obj->content.ptr.buffSz);//Copy data from user space to kernel space
              if (ret != LOS_OK) {
                  LiteIpcNodeFree(processID, buf);
                  return ret;
              }//Here we need to explain obj - > content ptr. Although the buff changes are all user space addresses, the meaning has changed for the second time. Although the data is the same, it points to the kernel space after the application is copied
              obj->content.ptr.buff = (VOID *)GetIpcUserAddr(processID, (INTPTR)buf);//Obtain the user space address of the process processID, so that the user space operation buf actually operates in the kernel space
              EnableIpcNodeFreeByUser(processID, (VOID *)buf);//Create an IPC node and hang it on the available linked list for reading
          } else {
              (VOID)LiteIpcNodeFree(processID, (VOID *)GetIpcKernelAddr(processID, (INTPTR)obj->content.ptr.buff));//Release IPC node in kernel space
          }
          return LOS_OK;
      }
    
  • The processID and taskID are filled in by the kernel. The application layer is not aware of the process and task. It is exposed to the service ID, svcidentity Handle, when the upper layer is used, it only needs to send / read messages to the service, and the service is created by the kernel and bound to tasks and processes. Therefore, as long as there is a service ID, you can query the corresponding process and task ID.
  • userID and gid involve user and group security modules. Please check the relevant articles in the series.

Processes and tasks

In addition, the two structures ProcIpcInfo and IpcTaskInfo LiteIPC realize the communication between processes, so there must be its location in the process control block, that is, ProcIpcInfo.

typedef struct {
    IpcPool pool;				///< IPC memory pool, which provides all memory related to kernel space allocation for IPC operation
    UINT32 ipcTaskID;			///< specify the task ID of ServiceManager
    LOS_DL_LIST ipcUsedNodelist;///< the linked list of nodes has been used, and the ippusednode node is hung on it. The memory of the applied ippusednode comes from the kernel heap space
    UINT32 access[LOSCFG_BASE_CORE_TSK_LIMIT];	///< which tasks are allowed to be accessed by the process through IPC
} ProcIpcInfo;

The process is just a housekeeper. What really makes the kernel busy is the task. There should also be a place for LiteIPC in the task control block, that is, IpcTaskInfo.

typedef struct {
    LOS_DL_LIST     msgListHead;///< the IPC nodes are hung one by one, and the ipplistnodes are hung on them. The memory of the applied ipplistnodes comes from the process IPC memory pool
    BOOL            accessMap[LOSCFG_BASE_CORE_TSK_LIMIT]; ///< should loscfg be used here_ BASE_ CORE_ PROCESS_ LIMIT ? @ note_ thinking 
    				///< can the task send IPC messages to other processes
} IpcTaskInfo;

The two structures are not complex. Hang the messages sent / replied to the corresponding linked list and provide mutual access functions between processes < - > tasks access and accessMap. Who will set the permissions? As mentioned above, it's HandleSvc.

IPC memory pool

And the last structure, IpcPool,

typedef struct {//Message passing in user space and kernel space is calculated by offset
    VOID   *uvaddr;	///< user space address, the address mapped from kvaddr. The relationship between these two addresses must be clear, otherwise the core idea of IPC cannot be understood
    VOID   *kvaddr;	///< kernel space address. IPC applies for kernel space, but it will map this address to user space through DoIpcMmap
    UINT32 poolSize; ///< IPC pool size
} IpcPool;

It is the basis for LiteIPC to realize the communication mechanism. It is a very clever place for kernel design. It realizes the function of reading kernel state data in user state. Think about how it does it?

Bai Wen said that the core | grasp the main context

  • Baiwen is equivalent to touching out the muscle and organ system of the kernel, which makes people start to feel plump and three-dimensional. Because it starts directly from the annotation source code, it is often sorted out every experience in the annotation process, and the following articles are slowly formed. The content is based on the source code and often uses life scenes as an analogy. Put the kernel knowledge points into a certain scene as much as possible, which has a sense of picture and is easy to understand and remember. It's important to say what others can understand! A hundred blogs are by no means Baidu's dogmatic talking about a bunch of awkward concepts, which is meaningless. More hope to make the kernel lifelike and feel more cordial.
  • Just as the code needs to be debug ged constantly, there will be many errors and omissions in the content of the article. Please forgive me, but it will be corrected repeatedly and updated continuously, V * XX represents the article serial number and the number of modifications. It is carefully crafted, concise and comprehensive, and strives to create high-quality content.
  • Bai Wen is posted at the "Hong Meng research station", "51CTO China CSDN", "the official account".

By function module:

Million note source code | buckle details everywhere

  • The purpose of annotating the core of millions of Chinese characters is to see its capillaries and cell structure clearly, which is equivalent to looking at the core with a magnifying glass. The kernel is not mysterious. It is easy to be addicted to find answers in the source code with questions. You will find that many articles interpret some problems incorrectly, or it is difficult to justify them if they are not profound. You will slowly form your own new interpretation, and the new interpretation will encounter new problems. You will advance layer by layer and roll forward. You are unwilling to let go with a magnifying glass.

  • < gitee | github | coding | codechina > The four big yards push the official source code, and the official account can be easily read back to millions.

Focus on not getting lost | code is life

Keywords: gitee liteos

Added by jonshutt on Mon, 07 Feb 2022 12:32:36 +0200