Call remote procedures rpc ultrasound scanner. Remote Procedure Call (RPC) and Remote Method Invocation (RMI). Troubleshooting crashes and errors

Call remote procedures RPC The concept of remote procedure call The idea behind Remote Procedure Call - RPC is to extend the well-known and understood mechanism for transferring control and data within a program running on one machine to transfer control and data over a network. Remote procedure call tools are designed to make it easier to organize distributed computing.The greatest efficiency of using RPC is achieved in those applications in which there is interactive communication between remote components with fast response times and a relatively small amount of data transferred.

Such applications are called RPC-oriented. The characteristic features of calling local procedures are Asymmetry, that is, one of the interacting parties is the initiator Synchronicity, that is, the execution of the calling procedure stops from the moment the request is issued and resumes only after returning from the called procedure. The implementation of remote calls is much more complicated than the implementation of calls to local procedures.

To begin with, since the calling and called procedures are executed on different machines, they have different address spaces, and this creates problems when passing parameters and results, especially if the machines are not identical. Since RPC cannot rely on shared memory, this means that RPC parameters should not contain pointers to non-stack memory locations and that parameter values ​​should be copied from one computer to another.

The next difference between RPC and a local call is that it necessarily uses the underlying communication system, but this should not be explicitly visible either in the definition of the procedures or in the procedures themselves. Remoteness contributes additional problems. The execution of the calling program and the called local procedure on the same machine is implemented within a single process. But the implementation of RPC involves at least two processes - one in each machine.

In case one of them crashes, the following situations may arise: if the calling procedure crashes, the remotely called procedures will become orphaned, and if the remote procedures crash, the calling procedures will become orphaned parents, who will wait in vain for a response from the remote procedures. In addition, there are a number of problems associated with the heterogeneity of programming languages ​​and operating environments, the data structures and procedure call structures supported in any one programming language are not supported in the same way in all other languages.

These and some other problems are solved by the widespread RPC technology, which underlies many distributed operating systems. Basic RPC OperationsTo understand how RPC works, let's first consider making a local procedure call on a regular machine running autonomously. Let it be, for example, the system call count read fd,buf,nbytes where fd is an integer, buf is an array of characters, nbytes is an integer .

To make the call, the calling procedure pushes the parameters onto the stack in the reverse order of Figure 3.1. After the read call is executed, it places the return value into a register, moves the return address, and returns control to the calling procedure, which fetches parameters from the stack, returning it to the initial state Note that in the C language parameters can be called either by reference by name or by value by value . In relation to the called procedure, value parameters are initialized local variables.

The called procedure can change them without affecting the original values ​​of these variables in the calling procedure. If a pointer to a variable is passed to the called procedure, then changing the value of this variable by the called procedure entails changing the value of this variable for the calling procedure. This fact is very significant for RPC. There is also another mechanism for passing parameters that is not used in C. It is called call-by-copy restore and involves the calling program copying variables onto the stack as values, and then copying them back after the call is made over the original values ​​of the calling procedure.

The decision about which parameter passing mechanism to use is made by the language developers. Sometimes this depends on the type of data being passed. In C, for example, integers and other scalar data are always passed by value, and arrays are always passed by reference.

Rice. 3.1. a The stack before the read call is executed b The stack during the execution of the procedure c The stack after returning to the calling program The idea behind RPC is to make a call to a remote procedure look as similar as possible to a call to a local procedure. In other words, to make RPC transparent, the calling procedure does not need to know that the called procedure is on another machine, and vice versa. RPC achieves transparency in the following way.

When the called procedure is actually remote, instead of the local procedure, another version of the procedure, called the client stub, is placed in the library. Similar to the original procedure, the stub is called using the calling sequence as in Figure 3.1, and an interrupt occurs when accessing the kernel. Only, unlike the original procedure, it does not place parameters in registers and does not request data from the kernel; instead, it generates a message to be sent to the kernel of the remote machine. Stages of RPC executionInteraction software components when performing a remote procedure call is illustrated in Figure 3.2. After the client stub has been called by the client program, its first task is to fill the buffer with the message being sent.

In some systems, the client stub has a single fixed-length buffer that is filled from the very beginning with each new request. In other systems, the message buffer is a pool of buffers for individual message fields, some of which are already full.

This method is especially suitable for cases where the packet has a format consisting of a large number of fields, but the values ​​of many of these fields do not change from call to call. The parameters must then be converted to the appropriate format and inserted into the message buffer. At this point, the message is ready to be sent, so the kernel call interrupt is executed. Rice. 3.2. Remote Procedure Call When the kernel gains control, it switches contexts, saves processor registers and memory map page handles, sets new map memory that will be used to run in kernel mode. Because the kernel and user contexts are different, the kernel must copy the message exactly into its own address space so that it can access it, remember the destination address and possibly other header fields, and it must pass it to the network interface.

This completes the work on the client side.

The transmission timer is turned on, and the kernel can either cyclically poll for a response or pass control to the scheduler, which will select some other process to run. In the first case, query execution is accelerated, but multiprogramming is absent. On the server side, incoming bits are placed by the receiving hardware either in the built-in buffer or in RAM.When all information has been received, an interrupt is generated.

The interrupt handler checks the packet's data for validity and determines which stub to pass it to. If no stub is expecting the packet, the interrupt handler must either buffer it or discard it altogether. If there is a waiting stub, the message is copied to it. Finally, a context switch is performed, as a result of which the registers and memory map are restored, taking the values ​​that they had at the moment when the stub made the receive call.

Now the server stub starts working. It unpacks the parameters and pushes them appropriately onto the stack. When everything is ready, a call to the server is made. After completing the procedure, the server transmits the results to the client. To do this, all the steps described above are performed, only in reverse order. Figure 3.3 shows the sequence of commands that must be executed for each RPC call, and Figure 3.4 shows what proportion of the total RPC execution time is spent on each of the 14 steps described.

The studies were conducted on a DEC Firefly multi-processor workstation, and although the presence of five processors necessarily affected the results of the measurements, the histogram shown in the figure gives general idea about the RPC execution process. Rice. 3.3. Stages of the RPC procedure Fig. 3.4. Time distribution between 14 stages of RPC execution 1. Call a stub 2. Prepare a buffer 3. Pack parameters 4. Fill in the header field 5. Calculate the checksum in the message 6. Interrupt to the kernel 7. Queue the packet for execution 8. Transfer the message to the controller via the QBUS bus 9. Transfer time Ethernet networks 10. Receive a packet from the controller 11. Interrupt handling procedure 12. Checksum calculation 13. Context switching to user space 14. Executing a server stub Dynamic binding Consider the question of how the client specifies the location of the server.

One method to solve this problem is to directly use network address server in the client program.

The disadvantage of this approach is that it is extremely inflexible when moving the server, or increasing the number of servers, or changing the interface; in all these and many other cases, it is necessary to recompile all programs that used hard setting of the server address. In order to avoid all these problems, in Some distributed systems use what is called dynamic linking.

The starting point for dynamic binding is to formally define the server specification. The specification contains the file server name, version number and a list of service procedures provided by this server to clients (Figure 3.5). For each procedure, a description of its parameters is given, indicating whether this parameter is input or output relative to the server. Some parameters can be both input and output - for example, some array that is sent by the client to the server is modified there, and then returned back to the client operation copy restore . Rice. 3.5. RPC Server Specification The formal server specification is used as input to the stub generator program, which creates both client and server stubs.

They are then placed in the appropriate libraries. When custom client program calls any procedure defined in the server specification, the corresponding stub procedure is associated with the program binary code.

Likewise, when a server is compiled, server stubs are associated with it. When the server starts, its very first action is to transfer its server interface special program, called binder. This process, known as the server registration process, involves the server transmitting its name, version number, unique identifier, and a descriptor of the server's location. The descriptor is system independent and can be an IP, Ethernet, X.500, or some other address.

In addition, it may contain other information, for example related to authentication. When a client calls one of the remote procedures for the first time, for example, read, the client stub sees that it is not yet connected to the server, and sends a message to the binder program with a request to import the interface of the desired version of the desired server. If such a server exists, then binder sends descriptor and unique identifier for the client stub.

When sending a message with a request, the client stub uses a descriptor as an address. The message contains parameters and a unique identifier that the server core uses to route the incoming message to the desired server if there are several of them on this machine. This method of importing and exporting interfaces is highly flexible. For example, there may be several servers supporting the same interface, and clients are randomly distributed among the servers.

Within the framework of this method, it becomes possible to periodically poll servers, analyze their performance and, in case of failure, automatically shut down, which increases the overall fault tolerance of the system. This method can also support client authentication. For example, the server may determine that it can only be used by clients from a specific list. However, dynamic binding has disadvantages, such as additional overhead and time spent exporting and importing interfaces.

The magnitude of these costs can be significant, since many client processes exist for a short time, and each time the process starts, the interface import procedure must be performed again. In addition, in large distributed systems, the binder program can become a bottleneck, and creating multiple programs with the same purpose also increases the overhead of creating and synchronizing processes. RPC semantics in the event of failures Ideally, RPC should function correctly in the event of failures.

Consider the following failure classes: 1. The client cannot locate the server, for example, if the desired server fails, or because the client program was compiled a long time ago and used old version server interface. In this case, in response to the client's request, a message containing an error code is received. 2. The request from the client to the server is lost. The simplest solution is through certain time repeat the request. 3. The response message from the server to the client is lost.

This option is more complicated than the previous one, since some procedures are not idempotent. An idempotent procedure is a procedure whose execution request can be repeated several times without changing the result. An example of such a procedure is reading a file. But the procedure for withdrawing a certain amount from a bank account is not idempotent, and if the response is lost, a repeated request can significantly change the state of the client’s account.

One of possible solutions is to bring all procedures to an idempotent form. However, in practice this is not always possible, so another method can be used - sequential numbering of all requests by the client kernel. The server core remembers the number of the most recent request from each client, and upon receiving each request, it analyzes whether this request is a primary or a repeated one. 4. The server crashed after receiving the request. The idempotency property is also important here, but unfortunately the approach with request numbering cannot be applied.

In this case, it matters when the failure occurred - before or after the operation. But the client kernel cannot recognize these situations; it only knows that the response time has expired. There are three approaches to this problem: Wait until the server reboots and try the operation again. This approach ensures that the RPC is completed at least once, and possibly more. Immediately report the error to the application.

This approach ensures that the RPC is executed at most once. The third approach does not guarantee anything. When the server fails, no support is provided to the client. The RPC may either not be executed at all, or it may be executed many times. In any case, this method is very easy to implement. Neither of these approaches is very attractive. And the ideal option, which would guarantee exactly one RPC execution, in the general case cannot be implemented for reasons of principle.

Let, for example, a remote operation be printing some text, which includes loading the printer buffer and setting one bit in some printer control register, as a result of which the printer starts. A server crash can occur either a microsecond before or a microsecond after the control bit is set. The moment of failure entirely determines the recovery procedure, but the client cannot find out about the moment of failure.

In short, the possibility of a server crash radically changes the nature of RPC and clearly reflects the difference between a centralized and a distributed system. In the first case, a server crash leads to a client crash, and recovery is impossible. In the second case, it is both possible and necessary to perform system recovery actions. 1. The client crashed after sending the request. In this case, calculations are performed on results that no one expects. Such calculations are called orphan calculations. The presence of orphans can cause various problems: overhead of CPU time, blocking of resources, substitution of the answer to the current request with the answer to the request that was issued client machine even before the system restarts.

How to deal with orphans? Let's look at 4 possible solutions. Destruction. Before the client stub sends an RPC message, it makes a note in the log indicating what it will do next. The log is stored on disk or other fault-tolerant memory.

After the accident, the system is rebooted, the log is analyzed and the orphans are eliminated. Disadvantages of this approach include, first, the increased overhead associated with writing each RPC to disk, and, second, possible inefficiency due to the appearance of second-generation orphans generated by RPC calls issued by first-generation orphans. Reincarnation. In this case, all problems are solved without using disk recording. The method consists of dividing time into sequentially numbered periods. When the client reboots, it broadcasts a message to all machines to announce the start of a new period.

After receiving this message, all remote calculations are eliminated. Of course, if the network is segmented, then some orphans may survive. Soft re-incarnation is similar to the previous case, except that not all deleted calculations are found and destroyed, but only the calculations of the rebooting client. Expiration. Each request is given a standard period of time T within which it must be completed.

If the request is not completed within the allotted time, then an additional quantum is allocated. Although this requires additional work, if after a client crash the server waits for an interval T before rebooting the client, then all orphans are necessarily destroyed. In practice, neither of these approaches is desirable; in fact, destroying orphans may make the situation worse. For example, suppose an orphan has locked one or more database files.

If the orphan is suddenly destroyed, then these locks will remain, in addition, the destroyed orphans may remain standing in various system queues, in the future they may cause the execution of new processes, etc.

What will we do with the received material:

If this material was useful to you, you can save it to your page on social networks:

Lecture 4

4.1 Remote Procedure Call Concept

The idea of ​​calling remote procedures (Remote Procedure Call - RPC) consists of extending the well-known and understood mechanism for transferring control and data within a program running on one machine to transferring control and data over a network. Remote procedure call tools are designed to facilitate the organization of distributed computing. The greatest efficiency of using RPC is achieved in those applications in which there is interactive communication between remote components with fast response times and a relatively small amount of data transferred. Such applications are called RPC-oriented.

The characteristic features of calling local procedures are: asymmetry, that is, one of the interacting parties is the initiator; synchronicity, that is, execution of the calling procedure stops from the moment the request is issued and is resumed only after the called procedure returns.

Implementing remote calls is much more complicated than implementing local procedure calls. To begin with, since the calling and called procedures are executed on different machines, they have different address spaces, and this creates problems when passing parameters and results, especially if the machines are not identical. Since RPC cannot rely on shared memory, this means that RPC parameters must not contain pointers to non-stack memory locations and that parameter values ​​must be copied from one computer to another. The next difference between RPC and a local call is that it necessarily uses the underlying communication system, but this should not be explicitly visible either in the definition of the procedures or in the procedures themselves. Remoteness introduces additional problems. The execution of the calling program and the called local procedure on the same machine is implemented within a single process. But the implementation of RPC involves at least two processes - one on each machine. If one of them crashes, the following situations may arise: if the calling procedure crashes, the remotely called procedures will become “orphaned”, and if the remote procedures crash, the calling procedures will become “orphaned parents”, waiting in vain for a response from the remote procedures.

In addition, there are a number of problems associated with the heterogeneity of programming languages ​​and operating environments: the data structures and procedure call structures supported in any one programming language are not supported in the same way in all other languages.


These and some other problems are solved by the widespread RPC technology, which underlies many distributed operating systems.

Basic RPC Operations

To understand how RPC works, let's first consider making a local procedure call on a typical machine running offline. Let this be, for example, a system call

count=read(fd,buf,nbytes);

where fd is an integer;

buf – array of characters;

nbytes is an integer.

To make the call, the calling procedure pushes the parameters onto the stack in reverse order. After the read call is executed, it places the return value into a register, moves the return address, and returns control to the calling procedure, which pops parameters from the stack, returning it to its original state. Note that in the C language, parameters can be called either by reference (by name) or by value (by value). In relation to the called procedure, value parameters are initialized local variables. The called procedure can change them without affecting the original values ​​of these variables in the calling procedure.

If a pointer to a variable is passed to the called procedure, then changing the value of this variable by the called procedure entails changing the value of this variable for the calling procedure. This fact is very significant for RPC.

There is also another parameter passing mechanism that is not used in C. It is called call-by-copy/restore, which requires the caller to copy variables onto the stack as values, and then copy them back after the call is made over the original values ​​of the calling procedure.

The decision about which parameter passing mechanism to use is made by the language developers. Sometimes it depends on the type of data being transferred. In C, for example, integers and other scalar data are always passed by value, and arrays are always passed by reference.

The idea behind RPC is to make a remote procedure call look as similar as possible to a local procedure call. In other words, make RPC transparent: the calling procedure does not need to know that the called procedure is on another machine, and vice versa.

RPC achieves transparency in the following way. When the called procedure is actually remote, another version of the procedure, called a client stub, is placed in the library instead of the local procedure. Like the original procedure, the stub is called using a calling sequence, and an interrupt occurs when accessing the kernel. Only, unlike the original procedure, it does not place parameters in registers and does not request data from the kernel; instead, it generates a message to be sent to the kernel of the remote machine.

RPC Execution Stages

The interaction of software components when performing a remote procedure call is illustrated in Figure 2.

Figure 2. Remote Procedure Call

After the client stub has been called by the client program, its first task is to fill the buffer with the message being sent. In some systems, the client stub has a single fixed-length buffer that is filled from the very beginning with each new request. In other systems, the message buffer is a pool of buffers for individual message fields, some of which are already full. This method is especially suitable for cases where the packet has a format consisting of a large number of fields, but the values ​​of many of these fields do not change from call to call.

The parameters must then be converted to the appropriate format and inserted into the message buffer. At this point, the message is ready to be sent, so the kernel call interrupt is executed.

When the kernel gains control, it switches contexts, saves processor registers and memory map (page handles), and installs a new memory map that will be used to run in kernel mode. Because the kernel and user contexts are different, the kernel must copy the message exactly into its own address space so that it can access it, remember the destination address (and possibly other header fields), and it must pass it to the network interface. This completes the work on the client side. The transmission timer is turned on, and the kernel can either cyclically poll for a response or pass control to the scheduler, which will select some other process to run. In the first case, query execution is accelerated, but multiprogramming is absent.

On the server side, incoming bits are placed by the receiving hardware either in an on-chip buffer or in RAM. When all information has been received, an interrupt is generated. The interrupt handler checks the correctness of the packet data and determines which stub it should be sent to. If none of the stubs are expecting this packet, the handler must either buffer it or discard it altogether. If there is a waiting stub, the message is copied to it. Finally, a context switch is performed, as a result of which the registers and memory map are restored, taking the values ​​that they had at the moment when the stub made the receive call.

Now the server stub starts working. It unpacks the parameters and pushes them appropriately onto the stack. When everything is ready, a call to the server is made. After executing the procedure, the server transmits the results to the client. To do this, perform all the steps described above, only in reverse order.

Figure 3 shows the sequence of commands that must be executed for each RPC call.

Figure 3. RPC procedure steps

After rebooting the computer the service did not start" Remote Procedure Call (RPC)". A lot depends on this service. As a result, system restore, network environment, sound, Windows Installer, the management console (MMC) almost does not work, does not appear on the taskbar open windows etc. and so on. An attempt to manually start results in the error " Cannot start...(RPC) on xxxComp. Error 5: Access denied"The antivirus found nothing. Two days of digging and the computer was brought back to life.

According to Microsoft's recommendation, the first thing I tried was to find and delete the registry key . I didn’t have it, perhaps as a result of some installed updates.

Next, an attempt to restore the service parameters in the registry. Since regedit.exe was read/delete only (another side effect), it was not possible to make changes. Yes, they were not needed, because... everything was right. It should look like this:

Windows Registry Editor Version 5.00 "Description"="Provides mapping of endpoints and other RPC services." "DisplayName"="Remote Procedure Call (RPC)" "ErrorControl"=dword:00000001 "Group"="COM Infrastructure" "ImagePath"=hex(2):25,00,53,00,79,00,73, 00,74,00,65,00,6d,00,52,00,6f,00,6f,00,\ 74,00,25,00,5c,00,73,00,79,00,73,00 ,74,00,65,00,6d,00,33,00,32,00,5c,00,73,\ 00,76,00,63,00,68,00,6f,00,73,00, 74,00,20,00,2d,00,6b,00,20,00,72,00,70,00,\ 63,00,73,00,73,00,00,00 "ObjectName"="NT AUTHORITY\\NetworkService" "Start"=dword:00000002 "Type"=dword:00000010 "FailureActions"=hex:00,00,00,00,00,00,00,00,00,00,00,00,01 ,00,00,00,00,00,00,\ 00,02,00,00,00,60,ea,00,00 "ServiceSidType"=dword:00000001 "ServiceDll"=hex(2):25.00 ,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,\ 00,74,00,25,00,5c,00, 73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,\ 72,00,70,00,63,00,73 ,00,73,00,2e,00,64,00,6c,00,6c,00,00,00 "Security"=hex:01,00,14,80,a8,00,00,00,b4, 00,00,00,14,00,00,00,30,00,00,00,02,\ 00,1c,00,01,00,00,00,02,80,14,00,ff,01 ,0f,00,01,01,00,00,00,00,00,01,00,00,\ 00,00,02,00,78,00,05,00,00,00,00,00, 14,00,8d,00,02,00,01,01,00,00,00,00,00,\ 05,0b,00,00,00,00,00,18,00,ff,01,0f ,00,01,02,00,00,00,00,00,05,20,00,00,00,\ 20,02,00,00,00,00,18,00,8d,00,02, 00,01,02,00,00,00,00,00,05,20,00,00,00,23,\ 02,00,00,00,00,14,00,9d,00,00,00 ,01,01,00,00,00,00,00,05,04,00,00,00,00,00,\ 18,00,9d,00,00,00,01,02,00,00, 00,00,00,05,20,00,00,00,21,02,00,00,01,01,00,\ 00,00,00,00,05,12,00,00,00,01 ,01,00,00,00,00,00,05,12,00,00,00 "0"="Root\\LEGACY_RPCSS\\0000" "Count"=dword:00000001 "NextInstance"=dword:00000001

Parameter value start may vary. You can still change the registry, but you need to boot from MS ERD commander.

I’ll simply describe the next steps point by point. The general idea is that you need to replace the files with ones that are known to work. They can be taken from another machine or from Windows distribution(like I did).

  • Launch the console (Start > Run: cmd)
  • cd z:\i386(Windows distribution there)
  • expand explorer.ex_ %TEMP%\explorer.exe
  • expand svchost.ex_ %TEMP%\svchost.exe
  • Launch Task Manager (Ctrl+Shift+Esc)
  • Stop exlporer.exe process
  • copy %TEMP%\explorer.exe %SYSTEMROOT% /y
  • Stop all svchost.exe processes. Attention! After this, you will have 60 seconds until the machine reboots.
  • copy %TEMP%\svchost.exe %systemroot%\system32 /y

This feint also did not produce results. Another option: run a scan of all protected system files with replacement wrong versions correct. In the console run:

sfc /PURGECACHE- Clear file cache and check files immediately
sfc /SCANONCE- One-time check on next boot

It didn’t help.. Then a completely brutal move is to restore the security settings. Again in the console:

secedit /configure /cfg %windir%\repair\secsetup.inf /db secsetup.sdb /verbose

After the reboot, the computer started working and basic services started. A new problem appeared (or maybe it was there from the very beginning): at least Disk Management Manager and Windows Installer did not start under my account. Access denied. You can restore access rights to the system disk to the “default” via the console:

secedit /configure /db %TEMP%\temp.mdb /Cfg %WINDIR%\inf\defltwk.inf /areas filestore

Then you need to manually define the rights for each account. or recreate them, whichever is easier.

In my case, I simply assigned the same rights to all system disk, using directory access as a reference. I added my domain account to the standard with full rights to the disk. Maybe this is wrong from a security point of view, but I don’t have time to delve into each directory separately.

What else could be done

While the computer was sick, this was not in the registry:

"ActiveService"="RpcSs"

Perhaps manual addition would somehow change the situation.

Attempts to manually start the service, for example through the command " net start rcpss"ended in error" Error 5: access denied". I assume access is denied because the service must be launched under the system account - "NT AUTHORITY". There is the following parameter in the registry:

"ObjectName"="NT AUTHORITY\\NetworkService"

I would try to enter the admin account here and start the service again. But this is only an idea that did not live up to implementation.

Another option: use the KiTrap0D exploit to obtain a console with system rights. This exploit was written about in . Actually a binary. But I have Windows updates, so it looks like this exploit no longer works.

Related materials:

Remote Procedure Call (RPC) Remote Procedure Call Concept

The idea of ​​a Remote Procedure Call (RPC) is to extend the well-known and understood mechanism for transferring control and data within a program running on one machine to transfer control and data over a network. Remote procedure call tools are designed to facilitate the organization of distributed computing. The greatest efficiency of using RPC is achieved in those applications in which there is interactive communication between remote components with fast response times and a relatively small amount of data transferred. Such applications are called RPC-oriented.

The characteristic features of calling local procedures are:

Asymmetry, that is, one of the interacting parties is the initiator; Synchronicity, that is, execution of the calling procedure stops from the moment the request is issued and is resumed only after the called procedure returns.

Implementing remote calls is much more complicated than implementing local procedure calls. To begin with, since the calling and called procedures are executed on different machines, they have different address spaces, and this creates problems when passing parameters and results, especially if the machines are not identical. Since RPC cannot rely on shared memory, this means that RPC parameters must not contain pointers to non-stack memory locations and that parameter values ​​must be copied from one computer to another. The next difference between RPC and a local call is that it necessarily uses the underlying communication system, but this should not be explicitly visible either in the definition of the procedures or in the procedures themselves. Remoteness introduces additional problems. The execution of the calling program and the called local procedure on the same machine is implemented within a single process. But the implementation of RPC involves at least two processes - one on each machine. If one of them crashes, the following situations may arise: if the calling procedure crashes, the remotely called procedures will become “orphaned”, and if the remote procedures crash, the calling procedures will become “orphaned parents”, waiting in vain for a response from the remote procedures.

In addition, there are a number of problems associated with the heterogeneity of programming languages ​​and operating environments: the data structures and procedure call structures supported in any one programming language are not supported in the same way in all other languages.

These and some other problems are solved by the widespread RPC technology, which underlies many distributed operating systems.

Basic RPC Operations

To understand how RPC works, let's first consider making a local procedure call on a typical machine running offline. Let this be, for example, a system call

Count=read(fd,buf,nbytes);

where fd is an integer,
buf - array of characters,
nbytes is an integer.

To make the call, the calling procedure pushes the parameters onto the stack in reverse order (Figure 3.1). After the read call is executed, it places the return value into a register, moves the return address, and returns control to the calling procedure, which pops parameters from the stack, returning it to its original state. Note that in the C language, parameters can be called either by reference (by name) or by value (by value). In relation to the called procedure, value parameters are initialized local variables. The called procedure can change them without affecting the original values ​​of these variables in the calling procedure.

If a pointer to a variable is passed to the called procedure, then changing the value of this variable by the called procedure entails changing the value of this variable for the calling procedure. This fact is very significant for RPC.

There is also another parameter passing mechanism that is not used in C. It is called call-by-copy/restore, which requires the caller to copy variables onto the stack as values, and then copy them back after the call is made over the original values ​​of the calling procedure.

The decision about which parameter passing mechanism to use is made by the language developers. Sometimes it depends on the type of data being transferred. In C, for example, integers and other scalar data are always passed by value, and arrays are always passed by reference.

Rice. 3.1. a) The stack before the read call is executed;
b) Stack during procedure execution;
c) Stack after returning to the calling program

The idea behind RPC is to make a remote procedure call look as similar as possible to a local procedure call. In other words, make RPC transparent: the calling procedure does not need to know that the called procedure is on another machine, and vice versa.

RPC achieves transparency in the following way. When the called procedure is actually remote, another version of the procedure, called a client stub, is placed in the library instead of the local procedure. Like the original procedure, the stub is called using a calling sequence (as in Figure 3.1), and an interrupt occurs when accessing the kernel. Only, unlike the original procedure, it does not place parameters in registers and does not request data from the kernel; instead, it generates a message to be sent to the kernel of the remote machine.

RPC Execution Stages

The interaction of software components when performing a remote procedure call is illustrated in Figure 3.2. After the client stub has been called by the client program, its first task is to fill the buffer with the message being sent. In some systems, the client stub has a single fixed-length buffer that is filled from the very beginning with each new request. In other systems, the message buffer is a pool of buffers for individual message fields, some of which are already full. This method is especially suitable for cases where the packet has a format consisting of a large number of fields, but the values ​​of many of these fields do not change from call to call.

The parameters must then be converted to the appropriate format and inserted into the message buffer. At this point, the message is ready to be sent, so the kernel call interrupt is executed.

Rice. 3.2. Remote Procedure Call

When the kernel gains control, it switches contexts, saves processor registers and memory map (page handles), and installs a new memory map that will be used to run in kernel mode. Because the kernel and user contexts are different, the kernel must copy the message exactly into its own address space so that it can access it, remember the destination address (and possibly other header fields), and it must pass it to the network interface. This completes the work on the client side. The transmission timer is turned on, and the kernel can either cyclically poll for a response or pass control to the scheduler, which will select some other process to run. In the first case, query execution is accelerated, but multiprogramming is absent.

On the server side, incoming bits are placed by the receiving hardware either in an on-chip buffer or in RAM. When all information has been received, an interrupt is generated. The interrupt handler checks the correctness of the packet data and determines which stub it should be sent to. If none of the stubs are expecting this packet, the handler must either buffer it or discard it altogether. If there is a waiting stub, the message is copied to it. Finally, a context switch is performed, as a result of which the registers and memory map are restored, taking the values ​​that they had at the moment when the stub made the receive call.

Now the server stub starts working. It unpacks the parameters and pushes them appropriately onto the stack. When everything is ready, a call to the server is made. After executing the procedure, the server transmits the results to the client. To do this, perform all the steps described above, only in reverse order.

Figure 3.3 shows the sequence of commands that must be executed for each RPC call, and Figure 3.4 shows what proportion of the total RPC execution time is spent on each of the 14 steps described. The tests were conducted on a DEC Firefly multi-processor workstation, and while the presence of five processors necessarily affected the results of the measurements, the histogram shown in the figure gives a general idea of ​​​​the RPC execution process.

Rice. 3.3. Steps to perform an RPC procedure

Rice. 3.4. Time distribution between 14 stages of RPC execution

1. Calling a stub

2. Prepare a buffer

3. Pack parameters

4. Fill in the title field

5. Calculate the checksum in the message

6. Interrupt to the kernel

7. Package queue for execution

8. Transmitting a message to the controller via the QBUS bus

9. Ethernet transmission time

10. Receive packet from controller

11. Interrupt handling procedure

12. Checksum calculation

13. Context switching to user space

14. Performing a server stub

Dynamic Linking

Let's consider how the client specifies the location of the server. One method to solve this problem is to directly use the server's network address in the client program. The disadvantage of this approach is its extreme inflexibility: when moving a server, or increasing the number of servers, or changing the interface in all these and many other cases, it is necessary to recompile all programs that used a hard-coded server address. To avoid all these problems, some distributed systems use what is called dynamic linking.

The starting point for dynamic binding is the formal definition (specification) of the server. The specification contains the file server name, version number and a list of service procedures provided by this server to clients (Figure 3.5). For each procedure, a description of its parameters is given, indicating whether this parameter is input or output relative to the server. Some parameters can be both input and output - for example, some array that is sent by the client to the server, modified there, and then returned back to the client (copy/restore operation).

Rice. 3.5. RPC Server Specification

The formal server specification is used as input to the stub generator program, which creates both client and server stubs. They are then placed in the appropriate libraries. When a user (client) program calls any procedure defined in the server specification, the corresponding stub procedure is associated with the program binary code. Likewise, when a server is compiled, server stubs are associated with it.

When a server starts up, the very first thing it does is pass its server interface to a special program called a binder. This process, known as the server registration process, involves the server passing its name, version number, unique identifier, and handle to the location of the server. The handle is system independent and can be an IP, Ethernet, X.500, or some other address, and may also contain other information, such as authentication-related information.

When a client calls one of the remote procedures for the first time, for example, read, the client stub sees that it is not yet connected to the server and sends a message to the binder program with a request to import the interface of the desired version of the desired server. If such a server exists, then binder passes the descriptor and unique identifier to the client stub.

When sending a message with a request, the client stub uses a descriptor as an address. The message contains parameters and a unique identifier that the server core uses to route the incoming message to the desired server if there are several of them on this machine.

This method of importing/exporting interfaces is highly flexible. For example, there may be multiple servers supporting the same interface, and clients are randomly distributed across the servers. Within the framework of this method, it becomes possible to periodically poll servers, analyze their performance and, in case of failure, automatically shut down, which increases the overall fault tolerance of the system. This method can also support client authentication. For example, the server may determine that it can only be used by clients from a specific list.

However, dynamic binding has disadvantages, such as additional overhead (time) for exporting and importing interfaces. The magnitude of these costs can be significant, since many client processes exist for a short time, and each time the process starts, the interface import procedure must be performed again. In addition, in large distributed systems, the binder program can become a bottleneck, and creating several programs with a similar purpose also increases the overhead of creating and synchronizing processes.

RPC semantics in case of failures

Ideally, RPC should function correctly even in the event of failures. Consider the following failure classes:

The client cannot locate the server, for example, if the desired server fails, or because the client program was compiled a long time ago and used an old version of the server interface. In this case, in response to the client's request, a message containing an error code is received. The request from the client to the server was lost. The simplest solution is to repeat the request after a certain time. The response message from the server to the client was lost. This option is more complicated than the previous one, since some procedures are not idempotent. An idempotent procedure is a procedure whose execution request can be repeated several times without changing the result. An example of such a procedure would be reading a file. But the procedure for withdrawing a certain amount from a bank account is not idempotent, and if the response is lost, a repeated request can significantly change the state of the client’s account. One possible solution is to make all procedures idempotent. However, in practice this is not always possible, so another method can be used - sequential numbering of all requests by the client kernel. The server core remembers the number of the most recent request from each client, and upon receiving each request, it analyzes whether this request is a primary or a repeated one. The server crashed after receiving the request. The property of idempotency is also important here, but unfortunately the approach with query numbering cannot be applied. In this case, it matters when the failure occurred - before or after the operation. But the client kernel cannot recognize these situations; it only knows that the response time has expired. There are three approaches to this problem: Wait until the server reboots and try the operation again. This approach ensures that the RPC completes at least once, and possibly more. Immediately report the error to the application. This approach ensures that the RPC is executed at most once. The third approach does not guarantee anything. When the server fails, no support is provided to the client. The RPC may either not be executed at all, or it may be executed many times. In any case, this method is very easy to implement.

Neither of these approaches is very attractive. And the ideal option, which would guarantee exactly one RPC execution, in the general case cannot be implemented for fundamental reasons. Let, for example, a remote operation be printing some text, which includes loading the printer buffer and setting one bit in some printer control register, as a result of which the printer starts. A server crash can occur either a microsecond before or a microsecond after the control bit is set. The moment of failure entirely determines the recovery procedure, but the client cannot find out about the moment of failure. In short, the possibility of a server crash radically changes the nature of RPC and clearly reflects the difference between a centralized and a distributed system. In the first case, a server crash leads to a client crash, and recovery is impossible. In the second case, it is both possible and necessary to perform system recovery actions.

The client crashed after sending the request. In this case, calculations are performed on results that no one expects. Such calculations are called "orphans". The presence of orphans can cause various problems: wasted CPU time, blocking of resources, substitution of a response to a current request with a response to a request that was issued by the client machine before the system was restarted.

How to deal with orphans? Let's look at 4 possible solutions.

Destruction. Before the client stub sends an RPC message, it makes a note in the log indicating what it will do now. The log is stored on disk or other fault-tolerant storage. After the accident, the system is rebooted, the log is analyzed and the orphans are eliminated. Disadvantages of this approach include, first, the increased overhead associated with writing each RPC to disk, and, second, possible inefficiency due to the appearance of second-generation orphans generated by RPC calls issued by first-generation orphans. Reincarnation. In this case, all problems are solved without using disk recording. The method consists of dividing time into sequentially numbered periods. When the client reboots, it broadcasts a message to all machines to announce the start of a new period. After receiving this message, all remote calculations are eliminated. Of course, if the network is segmented, then some orphans may survive. Soft re-incarnation is similar to the previous case, except that not all deleted calculations are found and destroyed, but only the calculations of the rebooting client. Expiration date. Each request is given a standard time period T during which it must be completed. If the request is not completed within the allotted time, then an additional quantum is allocated. Although this requires additional work, if after a client crash the server waits for an interval T before rebooting the client, then all orphans are necessarily destroyed.

In practice, neither of these approaches is desirable, and in fact, killing orphans may make the situation worse. For example, suppose an orphan has locked one or more database files. If the orphan is suddenly destroyed, then these locks will remain, in addition, the destroyed orphans may remain standing in various system queues, in the future they may cause the execution of new processes, etc.

The purpose of this article is to discuss terminology. The article is not about how and why, but only about the use of terminology. The article reflects the opinion of the author and does not pretend to be scientific.

Introduction

If you work in programming distributed systems or in systems integration, then most of what is presented here is not new to you.

The problem comes when people who use different technologies meet, and when those people start having technical conversations. In this case, mutual misunderstandings often arise due to terminology. Here I will try to bring together the terminologies used in different contexts.

Terminology

There is no clear terminology and classification in this area. The terminology used below is a reflection of the author’s model, that is, it is strictly subjective. Any criticism and any discussions are welcome.

I've divided the terminology into three areas: RPC (Remote Procedure Call), Messaging and REST. These areas have historical roots.

RPC

RPC technologies - the oldest technologies. The most prominent representatives of RPC are - CORBA And DCOM.

In those days, it was mainly necessary to connect systems in fast and relatively reliable local networks. The main idea behind RPC was to make calling remote systems much like calling functions within a program. The entire mechanics of remote calls were hidden from the programmer. At least they tried to hide it. Programmers in many cases were forced to work at a deeper level, where the terms marshaling appeared ( marshalling) And unmarshalling(how is that in Russian?), which essentially meant serialization. Normal function calls within processes were handled at the caller's end in Proxy, and on the side of the system performing the function, in Dispatcher. Ideally, neither the calling system nor the processing system would deal with the intricacies of transferring data between systems. All these subtleties were concentrated in the Proxy - Dispatcher bundle, the code of which was generated automatically.

So you won't notice, you shouldn't notice, any difference between calling a local function and calling a remote function.
Now there is a kind of RPC renaissance, the most prominent representatives of which are: Google ProtoBuf, Thrift, Avro.

Messaging

Over time, it turned out that the attempt to protect the programmer from the fact that the called function still differs from the local one did not lead to the desired result. The implementation details and fundamental differences between distributed systems were too great to be resolved using automatically generated Proxy code. Gradually, the understanding came that the fact that the systems are connected by an unreliable, slow, low-speed environment must be explicitly reflected in the program code.

Technologies have appeared web services. We started talking ABC: Address, Binding, Contract. It is not entirely clear why contracts appeared, which are essentially Envelopes for input arguments. Contracts often complicate the overall model rather than simplify it. But... it doesn't matter.

Now the programmer explicitly created service(Service) or client(Client) calling the service. The service consisted of a set operations (Operation), each of which took at the input request(Request) and issued answer(Response). Client explicitly sent(Sent) request, the service explicitly received ( Receive) and answered him (Sent), sending the answer. The client received a response and the call ended.

Just like in RPC, there was a Proxy and Dispatcher running somewhere. And as before, their code was generated automatically and the programmer did not need to understand it. Unless the client explicitly used classes from Proxy.

Requests and responses are explicitly converted to a format intended for transmission over the wire. Most often this is a byte array. The transformation is called Serialization And Deserialization and sometimes hides in the Proxy code.
The culmination of messaging manifested itself in the emergence of the paradigm ESB (Enterprise Service Bus). No one can really articulate what it is, but everyone agrees that data moves through the ESB in the form of messages.

REST

In the constant struggle with code complexity, programmers took the next step and created REST.

The main principle of REST is that function operations are sharply limited and left only a set of operations CRUD: Create - Read - Update - Delete. In this model, all operations are always applied to some data. The operations available in CRUD are sufficient for most applications. Since REST technologies in most cases imply the use of the HTTP protocol, the CRUD commands were reflected in the commands HTTP (Post - Get - Put - Delete) . It is constantly stated that REST is not necessarily tied to HTTP. But in practice, reflection of operation signatures onto the syntax of HTTP commands is widely used. For example, calling the function

EntityAddress ReadEntityAddress(string param1, string param2)

Expressed like this:

GET: entityAddress?param1=value1¶m2=value2

Conclusion

Before starting a discussion on distributed systems or integration, define the terminology. If Proxy will always mean the same thing in different contexts, then request, for example, will mean little in RPC terms, and marshalling will cause confusion when discussing REST technologies.