Course Content#
IPC - Inter-Process Communication
——Shared Memory——#
- The parent agrees on the shared data location before having children.
- Related interface: shm*
shmget#
Allocate a System V type shared memory segment [Segmented Memory]
[PS] The communication method of System V is still in use; its startup method has been abandoned.
- man shmget
- Prototype
- Return value type: int
- Parameter types: key_t, size_t, int
- ipc → Inter-Process Communication
- Description
- The key passed in and the id returned are linked together.
- The size of the new shared memory segment corresponds to the integer page size [rounded up].
- Creating shared memory requires a flag: IPC_CREAT.
- If IPC_CREAT is not present, it will check the user's access permissions for the segment corresponding to the key.
- Note: At least 9 bits need to be specified for permissions, such as 0600, the leading 0 cannot be omitted.
- Return value
- On success, returns a valid memory identifier [id]; on error, returns -1 and sets errno.
- After obtaining the id through the key, how to find the corresponding address? shmat 👇
shmat, shmdt#
Shared memory operations [attach, attach; detach, detach]
- man shmat
- Prototype
- Note that the return value of shmat is: void *
- Description
- shmat
- Attaches the shared memory segment specified by the id to the calling process's own address space.
- Each process believes it is using a separate contiguous memory block [Virtual Memory Technology].
-
- Refer to Wikipedia
-
- Each process believes it is using a separate contiguous memory block [Virtual Memory Technology].
- shmaddr specifies the attachment address:
- NULL: The system automatically attaches [commonly used].
- Otherwise, attaches to the automatically rounded down address [defined in shmflg as SHM_RND] or manually ensures page-aligned addresses.
- A successful call will update the shared memory structure shmid_ds.
- shm_nattach: Number of attachments. The real physical space corresponding to the shared memory may be attached by multiple processes.
- shmdt
- This is the opposite operation of shmat.
- The operation must be on the currently attached address.
- shmat
- Return value
- On success, shmat returns the address, shmdt returns 0.
- On error, returns -1 and sets errno.
- The id is unique throughout the system, and the same id must correspond to the same physical memory.
- [Note] The same id returns different addresses through shmat because it appears as independent address spaces in different processes [Virtual Memory concept].
- By the way, shmget obtains the id through the key, shmat obtains the memory address through the id, but how is the [key] obtained? 👇
ftok#
Converts a pathname and a project identifier into a System V IPC key.
- man ftok
- Prototype
- Requires a pathname and an int type variable to complete the conversion.
- Description
- The file must exist and be accessible.
- proj_id must have at least 8 valid bits and cannot be 0.
- The same filename and proj_id correspond to the same return value.
- Therefore, fixed input parameters will return a fixed key.
- Return value
- On success, returns the key; on failure, returns -1 and sets errno.
shmctl#
Control of shared memory.
- man shmctl
- Prototype
- cmd: command, int type. It can be predicted to be some uppercase macro definitions.
- Description
- You can see the detailed information of the shmid_ds structure.
- Some commands:
- IPC_STAT: Copies the shmid_ds structure information to buf [requires read permission].
- IPC_SET: Modifies the shmid_id structure.
- IPC_RMID: Marks the segment to be destroyed.
- The segment will only be truly destroyed when it is not attached.
- No buf variable is needed, just set it to NULL.
- [PS] Must check if it is really destroyed, otherwise there may be remnants [can be checked through return value].
- IPC_INFO [Linux specific, avoid using for compatibility].
- Return value
- Generally: 0, success [except INFO, STAT]; -1, error.
——Thread Related——#
From the thread library [pthread]
Mutex, condition variable
[PS] Multi-threading, high concurrency, when using shared memory, mutual exclusion must be considered.
pthread_mutex_*#
【Mutex】 operations mutex
- man pthread_mutex_init, etc.
- lock: When using lock, it will check if it is already locked; if locked, it will suspend until unlocked [possible blocking point].
- init: Dynamic initialization method, requires the use of attribute variable.
- Attribute interface: pthread_mutexattr_*
- init: Initialization
- In the initialization function, attr is an output parameter, return value is 0.
- setpshared: Sets inter-process sharing.
- pshared variable is of int type, which is actually a flag.
- 0: Process private; 1: Process shared → corresponding to macros PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED.
- init: Initialization
- Basic operations of mutex: Create attribute variable 👉 Initialize mutex 👉 Lock/Unlock operations, see code demonstration——Shared Memory [Mutex].
pthread_cond_*#
【Condition Variable】 Control Conditions
- man pthread_cond_init, etc.
- Structure is very similar to mutex.
- A condition variable is a synchronization device that allows threads to suspend and release processor resources until a certain condition is met.
- Basic operations: Send condition signal, wait for condition signal.
- ⭐ Must be associated with a mutex to avoid race conditions: It is possible that one thread is ready to wait for a condition before another thread has already sent the signal, leading to missed messages.
- init: Initialization can use attribute variables [but in Linux thread implementation, attribute variables are actually ignored].
- signal: Only wakes up 1 thread that is waiting; if there are no waiting threads, nothing happens.
- broadcast: Wakes up all waiting threads.
- wait
- ① Unlocks mutex and waits for the condition variable to activate [true atomic operation].
- ② Before wait returns [before the thread restarts / after receiving the signal], it will relock the mutex.
- [PS]
- When calling wait, the mutex must be in a locked state.
- While waiting, it does not consume CPU.
- ❗ The segment in the yellow box in the image
- Ensures that during the wait unlocks the mutex -> prepares to wait [atomic operation], other threads will not send signals.
- ❓ At this time, the mutex is already unlocked, so this atomic operation may still need to use something similar to a mutex.
- ❓ It seems that wait can prepare to wait first -> then unlock the mutex, which is normal logic, but unlocking first and then performing an atomic operation, what is the significance of unlocking first?
Code Demonstration#
Shared Memory [Without Locking]#
5 processes demonstrate the cumulative sum from 1 to 10000.
- ❗ Here, the child processes directly use the shared memory address share_memory inherited from the parent process [virtual address].
- It can be seen that the same virtual address in different processes points to the same shared memory [physical memory].
- If the child processes obtain the shared address through the inherited shmat and id, it will correspond to a new virtual address in the child process, but still point to the same shared memory; the originally inherited shared address share_memory can still be used.
- ❗ How to achieve true destruction after using shared memory.
- Use shmdt to detach the shared memory attached in all processes [experimentally, this step can be omitted because the process will automatically detach upon termination].
- Then use shmctl with IPC_RMID to destroy the shared memory segment [remove shmid].
- [PS]
- Each process counts as one attachment, and the shared memory segment will only be truly destroyed when the attachment count is 0.
- You can also remove the IPC_EXCL flag from shmget to not check if it already exists, but this is not a fundamental solution.
- If the above shmctl operation is not performed
- The first execution is fine, indicating that the shared memory segment was successfully created.
- But the second execution shows that the file already exists.
-
- This indicates that the shared memory was not automatically destroyed after the last execution, and the second execution still uses the same key to create shared memory [each time the same key corresponds to different shmid].
-
- [Exploration Process]
- ipcs: Displays IPC-related resource information.
- You can see the undestroyed shared memory, as well as message queues and semaphores.
- And their keys, ids, permissions perms.
- You can also see that their nattch is 0, indicating that there are no attachments.
- ipcrm: Deletes IPC resources.
- The usage of parameters can be viewed through --help.
- Resources can be removed by key or id.
- Manually delete resources and then run the program:
ipcs | grep [owner] | awk '{print $2}' | xargs ipcrm -m && ./a.out
.
- ipcs: Displays IPC-related resource information.
- ❗ The essence of the error as follows [personal understanding]
- The correct cumulative sum from 1 to 10000 should be 50005000.
- Multiple processes simultaneously operating on shared memory [or] a read/write operation of one process is not fully completed before another process starts a new operation. For example:
- One process just increments now++, writes to memory, and at this moment another process gets the new now and increments it again, adding the now incremented twice to the old sum.
- This means that the operations now++ and sum accumulation are not atomic operations.
- However, generally, the CPU speed is too fast, so it is very likely that the operations are atomic [now++ and sum accumulation writing to memory], with a very small probability of error.
- Multi-core systems are more likely to encounter calculation errors because a single processor can only run one process at a time.
- [PS]
- The first 0 in the permission setting 0600 indicates the use of octal.
- usleep(100): Makes the process sleep for 100ms, allowing one process to compute once and block, letting other processes continue, purely to reflect division of labor.
- Add header files according to the man manual.
Shared Memory [Mutex]#
A more efficient lock in memory, similar to file locks [refer to example] concept.
- Create a mutex: Create attribute variable 👉 Initialize mutex.
- Two conditions for using mutex between processes:
- ① The mutex variable is placed in shared memory, shared with each process, thus controlling each process.
- ② When creating the mutex in the parent process, the created attribute variable is set for process sharing [by default, it only works between threads].
- However, some kernels may not require this operation, but for compatibility, it is recommended to set it ❗.
- [PS]
- After calculating the cumulative sum, remember to unlock.
- The slowest operation in the system is IO operations, so unlocking before printf allows the mutex to be released earlier, which is more efficient.
- fflush can manually flush the buffer to avoid output confusion.
Shared Memory [Condition Variable]#
Each process calculates 100 times in turn.
- Initialization of cond condition variable is similar to mutex.
- In the implementation of Linux threads, attribute variables are actually not needed; same as mutex.
- For single-core machines, before sending a condition signal, you need to usleep for a while to ensure that the child process is ready to wait for the signal. Otherwise,
- For the parent process, it executes first, sends the signal, and if the child process has not yet had its turn to run, it will miss the signal.
- For child processes, it is similar; before sending the signal, let other child processes run to the wait state first [in practice, usleep cannot guarantee this].
- ❗ Note
- There must be a locked mutex before wait.
- Remember to unlock + send signal operation after each operation [after 100 calculations / completion of 10000 accumulation].
- There are two sequences for sending signal operations:
- ① Lock -> Send signal -> Unlock
- Disadvantage: The waiting thread is awakened from the kernel [→ user mode], but because there is no mutex available to lock, unfortunately, it returns [from user mode] to kernel space until there is a mutex available to lock.
- Two context switches [between kernel mode and user mode] waste performance.
- Advantage:
- Ensures thread priority [❓].
- In the Linux thread implementation, there are cond_wait queues and mutex_lock queues, so it will not return to user mode, thus avoiding performance loss.
- Disadvantage: The waiting thread is awakened from the kernel [→ user mode], but because there is no mutex available to lock, unfortunately, it returns [from user mode] to kernel space until there is a mutex available to lock.
- ② Lock -> Unlock -> Send signal [this article adopts].
- Advantage: Ensures that threads in the cond_wait queue have a mutex available to lock.
- Disadvantage: A low-priority thread that grabs the mutex may execute first.
- Reference: Condition Variable pthread_cond_signal, pthread_cond_wait——CSDN.
- ① Lock -> Send signal -> Unlock
- Implementation Effect
-
- Each process calculates 100 times in turn.
- [PS] Even usleep cannot completely avoid message omission; a variable in shared memory can be used to record the signal sent; additionally, there may also be cases of false wake-ups.
- Comprehensive solutions can refer to False Wake-up && Message Omission——CSDN.
-
if (!count) { // -> Message omission [not yet in wait state]
pthread_mutex_lock(&lock);
while (condition_is_false) { // -> False wake-up [multiple awakenings simultaneously]
pthread_cond_wait(&cond, &lock);
}
//...
pthread_mutex_unlock(&lock);
}
Simple Chat Room#
- chat.h
-
- Username + message + standard for using shared memory.
-
- 1.server.c
- The logic is basically similar to the previous code demonstration; pay attention to the subsequent clearing operations, mainly for data safety.
- 2.client.c
- Focus on the role of the while loop on line 41: Prevents the client from grabbing the lock, causing the server to not receive the signal.
- There is also a hidden danger: A client may fail to grab the lock and become blocked.
- [Note] When compiling 2.client.c with gcc, be sure to add -lpthread; otherwise, there will be no error, but at runtime, the server will not receive the signal.
- Effect Demonstration
- Left is the server, right are two clients.
Additional Knowledge Points#
- Related processes: between parent and child, between siblings.
Points to Consider#
- Processes competing to calculate VS. Each process calculating one hundred times, which method is more efficient?
- The latter is more efficient; thinking to the extreme, one process calculating everything is more efficient.
- This involves the throughput of the CPU; this accumulation is CPU-intensive, not IO-intensive.
- Refer to 4 Advanced Process Management——Processor-Constrained and IO-Constrained Processes.
Tips#
- Command to view IPC-related resource information: ipcs.
- For user threads, if one thread in a process crashes, all threads in the entire process will crash.
- Movie recommendation before class: "Dances with Wolves" 1990.
- Experience different cultures, with many quiet and beautiful scenes.
- https://pan.baidu.com/s/1hqxmTAYyd3iCOyGdCTPmYw
- Extraction code: yqed