Added translations for blog posts

This commit is contained in:
ClovertaTheTrilobita 2026-03-29 23:28:15 +03:00
parent b125771da9
commit 0db073010b
14 changed files with 1831 additions and 32 deletions

View file

@ -1,24 +1,440 @@
--- ---
title: '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试' title: "(Step-by-Step Code Explanation) Building a QQ Group Chat Bot with Nonebot2 + Official API"
pubDate: 2022-07-01 pubDate: 2025-04-14
description: 'This is the first post of my new Astro blog.' description: "Built a QQ bot with Nonebot2 and the official API, together with the group homies."
author: 'Astro Learner' author: "Cloverta"
image: image:
url: 'https://s2.loli.net/2022/05/01/UNzy8c6pTHBSuMO.jpg' url: "https://files.seeusercontent.com/2026/03/25/fiG2/pasted-image-1774456117575.webp"
alt: 'The Astro logo on a dark background with a pink glow.' alt: "img"
tags: ["astro", "blogging", "learning in public", "Hello"] tags: ["Python", "Nonebot2", "QQ Bot", "Tutorial"]
--- ---
Welcome to my _new blog_ about learning Astro! Here, I will share my learning journey as I build a new website. <p style="font-size: 0.85rem;"><i><sub>Content translated by <a href="https://www.deepseek.com/">DeepSeek</a>.</sub></i></p>
## What I've accomplished Previously, together with the group homies, we built a group chat bot based on Nonebot2's QQ adapter.
1. **Installing Astro**: First, I created a new Astro project and set up my online accounts. Code Repository: [GitHub - ClovertaTheTrilobita/SanYeCao-Nonebot: A QQ group chat bot based on Nonebot + Official API](https://github.com/ClovertaTheTrilobita/SanYeCao-Nonebot)
2. **Making Pages**: I then learned how to make pages by creating new `.astro` files and placing them in the `src/pages/` folder. For the basic configuration of the Nonebot2 QQ adapter + QQ Open Platform tutorial, **it's recommended to first follow this to get Nonebot configured**:
3. **Making Blog Posts**: This is my first blog post! I now have Astro pages and Markdown posts! [(QQ Bot Setup) Building an Official QQ Bot with NoneBot2 - Easy to Get Started - CSDN Blog](https://blog.csdn.net/weixin_58403216/article/details/144715878)
## What's next Nonebot Official Documentation:
I will finish the Astro tutorial, and then keep adding more posts. Watch this space for more to come. [Quick Start | NoneBot](https://nonebot.dev/docs/quick-start)
QQ Discussion Group: 710101225
Welcome to join the discussion~
# I. What You Need to Know Before Starting
### 0. First, an Editor (Required)
It is recommended to use [PyCharm](https://www.jetbrains.com/zh-cn/pycharm/) or [Visual Studio Code](https://code.visualstudio.com/)
If you are a vim master or a terrifying Notepad user, forget I said anything (jk).
### 1. Basic Python Syntax (Required)
If you are not familiar with Python or have no programming foundation, it is suggested to refer to:
[Python3 Tutorial](https://www.runoob.com/python3/python3-tutorial.html)
### 2. Basic Git Usage
[Git Installation and Configuration Tutorial (Beginner's Guide 2024 Latest Edition) - CSDN Blog](https://blog.csdn.net/weixin_44406127/article/details/137540031)
### 3. Anaconda Python Distribution (Recommended)
[Latest and Most Detailed Anaconda Beginner Installation + Configuration + Environment Creation Tutorial - CSDN Blog](https://blog.csdn.net/qq_44000789/article/details/142214660)
# II. Let's Get Started
As everyone knows, the hardest step in computing is setting up the environment. If you've completed the above steps, you're 90% of the way to success (not really).
## 1. Nonebot2 Project Structure
The typical Nonebot2 project structure is:
**bot.py # Program entry point**
and
**plugins # Plugins**
`bot.py` is the program we need to run when starting the bot backend. For specific events, like the command "/weather", we write the program in the `plugins` directory.
In `bot.py`, we generally configure it as follows:
```python
import nonebot
from nonebot.adapters.qq import Adapter as QQAdapter
from nonebot import logger
from nonebot.log import default_format, default_filter
nonebot.init() # Initialize nonebot
driver = nonebot.get_driver() # Set the driver
driver.register_adapter(QQAdapter) # Register the QQ adapter
nonebot.load_builtin_plugins('echo', 'single_session') # Add some built-in plugins
nonebot.load_from_toml("pyproject.toml") # Load configuration file
logger.add("error.log", level="ERROR", format=default_format, rotation="1 week") # Log output
if __name__ == "__main__":
nonebot.run()
```
Of course, just understand the above code. What we mainly need to figure out are Nonebot's event matchers and event handlers.
## 2. Event Matchers
When using a QQ group chat bot, we often see commands sent to the bot, like "/weather", and the bot replies with the corresponding content. How is this implemented?
### 1) The `on_command()` Matcher
Nonebot2 provides a very convenient matcher: `on_command()`
Here's the code:
```python
from nonebot import on_command
from nonebot.rule import to_me
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
@weather.handle()
async def get_weather():
await weather.send("The weather is...")
```
![](images/193b4709-1d76-48b0-8288-3b557e959773)
In the above code, we used the `on_command()` matcher to create the simplest bot command response function.
Its logic is:
After the bot receives the "/weather" command, it calls the `get_weather()` method: sends a message "The weather is..." to the QQ group.
> *Parameters in the `on_command()` matcher:*
>
> *`"天气"`: When the "/weather" command is received, trigger the matcher.*
>
> *`rule=to_me()`: The command is directed at me (the bot).*
>
> *`aliases={"weather", "查天气"}`: Command aliases. When a user sends "/weather" or "/查天气", this matcher is also triggered.*
>
> *`priority=10`: Priority is 10. The smaller the number, the higher the priority. For example, if there is another matcher for the same "/weather" command but with `priority=9`, then the matcher with `priority=9` will be triggered first.*
>
> *`block=True`: After this matcher is triggered, other matchers will not be triggered. In the previous example, if the `priority=9` matcher is triggered and its `block=True`, then this `priority=10` matcher with the same name will not be triggered.*
### 2) The `on_keyword()` Matcher
It's quite similar to `on_command()`.
```python
from nonebot import on_keyword
from nonebot.rule import to_me
weather = on_keyword({"天气", "weather", "查天气"}, rule=to_me(), priority=10, block=True)
@weather.handle()
async def get_weather():
await weather.send("The weather is...")
```
In the above example, **as long as** the message sent by the user **contains** **any one or more** of the three words "天气", "weather", "查天气", this matcher will be triggered.
### 3) The `on_message()` Matcher
This matcher is triggered whenever a user sends a message to the bot. We generally use it to handle non-standard user input.
```python
from nonebot import on_message
from nonebot.rule import Rule, to_me
from nonebot.adapters.qq import MessageEvent
menu = ["/天气", "/查天气", "/摸摸"]
async def check_value_in_menu(message: MessageEvent) -> bool:
value = message.get_plaintext().strip().split(" ") # Get the plain text sent by the user and split it into a string array by spaces.
if value[0] in menu: # If the first element in the array is in the 'menu' array, return False.
return False
else:
return True
check = on_message(rule=to_me() & Rule(check_value_in_menu), block=True, priority=10)
@check.handle()
async def handle_function(message: MessageEvent):
await check.finish("Please enter a valid command.")
```
We can see that the `on_message()` matcher no longer requires passing a string like the previous two matchers.
Suppose the user correctly inputs the command: "/天气"
`on_message()` receives the message at this point, so it calls the `check_value_in_menu()` method to check if the command is in the `menu` list.
Clearly it exists, so the `check_value_in_menu()` method returns `False`, which does not satisfy the trigger condition of `on_message()`, so this matcher will not be triggered.
Conversely, if the user sends "/天气是?", after `on_message()` receives the message, `check_value_in_menu()` detects that this command is not in the `menu` list and returns `True`.
At this point, the trigger condition for `on_message()` is satisfied, it sends "Please enter a valid command." to the user and terminates this session.
## 3. Getting Message Content
Our bot often needs to get the text content of the user's input in many applications.
For example, in the weather module mentioned above, our usual method is to send "/weather [location]" to the bot to query the weather for that area.
How do we get the text content of the user's message?
### 1) The Message Object
> The method of using dependency injection to obtain contextual information is very simple. We only need to declare the required dependencies in the function parameters and correctly add the function as an event handler dependency. In NoneBot, we can directly use the parameter types defined in the `nonebot.params` module to declare dependencies.
This is the explanation given by the Nonebot official documentation. It looks quite abstract, doesn't it?
Let's look at the code:
```python
from nonebot import on_command
from nonebot.rule import to_me
from nonebot.adapters import Message
from nonebot.params import CommandArg
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
@weather.handle()
async def get_weather(args: Message = CommandArg()):
# Extract the plain text parameter as the location and check if it's valid.
if location := args.extract_plain_text():
await weather.finish(f"Today's weather in {location} is...")
else:
await weather.finish("Please enter a location.")
```
This is a slight modification to the previous weather module. When the matcher receives the "/weather" command, we save the Message object in the `args` variable, allowing us to get the text information entered by the user.
> There can be no space between the command and the parameter. The information obtained by `CommandArg()` is the content following the command with leading whitespace removed. For example: the parameter for the message "/天气 上海" is `上海`.
If the user sends "/天气 海口" to the bot, the bot will reply in the group chat: "Today's weather in Haikou is..."
### 2) The MessageEvent Object
This is equivalent to a more versatile Message object. Through it, you can get the user's [member_openid and group_openid](https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/unique-id.html). The official documentation hasn't been updated for a long time; actually, user IDs are now like this:
> - Different `bots` in a group chat scenario obtain different group unique identifiers `openid`, called `group_openid`.
> - The same `bot` in different groups obtains the same unique identifier `openid` for the same user within the group, called `member_openid`.
> - Different `bots` in the same group chat obtain different unique identifiers `openid` for the same user within the group.
So don't think about doxxing others, because you have no idea who they are.
Getting the user's unique identifier ID has many uses. For example, you can send different daily fortunes (a bot fashion accessory) to different users, or get a group "waifu," etc.
How to get it:
```python
from nonebot.adapters.qq import MessageEvent
from nonebot.plugin import on_command
from nonebot.rule import to_me
test = on_command("/test", rule=to_me(), priority=10, block=True)
@test.handle()
async def test_method(message: MessageEvent):
member_openid = message.get_user_id()
```
Use the `get_user_id()` method from the MessageEvent object. It returns a string storing the user ID, which looks like a bunch of gibberish.
## 4. Sending Messages
Actually, we've already mentioned message sending in our previous examples. The most commonly used sending methods are `send()` and `finish()`.
### 1) The `send()` Method
We can see its basic usage in the earliest example:
```python
await weather.send("The weather is...")
```
This means sending a message to the group chat with the content "The weather is..."
### 2) The `finish()` Method
It is used similarly to `send()`. The only difference is: after using the `finish()` method, this session ends, and no more messages can be sent in this session.
For example, using the `send` method:
```python
from nonebot import on_command
from nonebot.rule import to_me
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
@weather.handle()
async def get_weather():
await weather.send("Querying for you, please wait~")
await weather.send("The weather is...")
await weather.finish("Query complete!")
```
When the user sends the "/weather" command, the bot will reply with the above three messages in sequence.
Note: `finish()` is equivalent to a `return` statement within a method. Code after the `finish()` method will not be executed.
> Warning: Since `finish` ends the event by raising a `FinishedException`, the exception might be caught by an unrestricted `try-except`, affecting the normal processing flow of the event and preventing it from ending properly. Please be sure to specify the error type in the exception catch or exclude all exceptions of type [MatcherException](https://nonebot.dev/docs/api/exception#MatcherException), or move `finish` outside the catch scope for use.
### 3) The MessageSegment Object
We often see QQ bots sending pictures, videos, etc., in group chats. It looks really enviable. Let's implement this feature now.
① Sending Images
Nonebot provides two sending methods: sending local images and sending images via URL.
Let's first look at local sending: using `MessageSegment.file_image()`
```python
from pathlib import Path
from nonebot.rule import to_me
from nonebot.plugin import on_command
from nonebot.adapters.qq import MessageSegment
image = on_command("本地图", rule=to_me(), priority=10, block=True)
@image.handle()
async def handle_function():
local_image_path = "path/to/your/image.jpg"
await image.finish(MessageSegment.file_image(Path(local_image_path)))
```
Note: `MessageSegment.file_image()` does not receive a string, so we need to use `Path()` to convert it into a Path object.
Sending URL images is equally simple: use `MessageSegment.image()`
```python
from nonebot.rule import to_me
from nonebot.plugin import on_command
from nonebot.adapters.qq import MessageSegment
image = on_command("在线图", rule=to_me(), priority=10, block=True)
@image.handle()
async def handle_function():
url = "www.example.com/image.jpg"
await image.finish(MessageSegment.image(url))
```
Note that the link must be a direct link to the image, meaning the link ends with xxx.jpg or png.
② Sending Videos
Like images, Nonebot can send local videos and online videos.
Simply use:
```python
MessageSegment.video(video_url) # Send a video from a URL link
MessageSegment.file_video(Path("path/to/your/video.mp4")) # Send a local video
```
③ Image and Text Side-by-Side
Sometimes, we want to send a message with an image, just like in the QQ client. This is also very easy to implement:
```python
from nonebot.rule import to_me
from nonebot.plugin import on_command
from nonebot.adapters.qq import Message, MessageSegment
image_text = on_command("图文", rule=to_me(), priority=10, block=True)
@image_text.handle()
async def handle_function():
url = "www.example.com/image.jpg"
content = "Here's your picture"
msg = Message([
MessageSegment.image(url),
MessageSegment.text(content),
])
await image_text.send(msg)
```
## 5. Advanced Play (Actually Basic Play)
The above are several commonly used basic methods. Now we've reached the most exciting part: practical application.
For example, we can create a new `weather.py` in the project's `src/plugins/` directory.
Use a weather API to build a weather query function.
```python
from nonebot.rule import to_me
from nonebot.plugin import on_command
from nonebot.adapters import Message
from nonebot.params import CommandArg
weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
@weather.handle()
async def handle_function(args: Message = CommandArg()):
# Extract the plain text parameter as the location and check if it's valid.
if location := args.extract_plain_text():
# Call the weather query API to get weather data.
weather_data = format_weather(location)
await weather.finish(weather_data)
else:
await weather.finish("Please enter a location.")
import requests
def get_weather(location):
# Set the request URL and parameters.
url = f'https://apis.juhe.cn/simpleWeather/query?key=50a3bd415158e186903d6e6994157589&city={location.rstrip("市").rstrip("县").rstrip("区")}'
# Send a GET request.
response = requests.get(url)
# Check if the request was successful.
if response.status_code == 200:
# Parse the returned JSON data.
data = response.json()
# Check if the query was successful.
if data['reason'] == '查询成功!' or data['reason'] == '查询成功':
# Return the weather data.
return data['result']
else:
return {"error": "Query failed: " + data['reason']}
else:
return {"error": "Request failed, status code: " + str(response.status_code)}
# Call the function and process the returned weather data.
def format_weather(location):
# Assume you already have the URL-encoded city name here. Using '%E9%87%8D%E5%BA%86' as an example forqing.
city_encoded = location # URL-encoded for the city
weather_data = get_weather(city_encoded)
# Check if an error was returned.
if 'error' in weather_data:
return weather_data['error']
else:
# Real-time weather.
realtime_weather = weather_data['realtime']
result = "\n" + location.rstrip("市").rstrip("县").rstrip("区") + f" Real-time Weather:" + "\n" + f"{realtime_weather['info']}, Temperature: {realtime_weather['temperature']}℃, Humidity: {realtime_weather['humidity']}%, Wind Direction: {realtime_weather['direct']}, Wind Force: {realtime_weather['power']} level, AQI: {realtime_weather['aqi']}"
# Weather for the next few days.
result = result + "\n" + "Weather for the next few days:🌤⛈️☔️"
for day in weather_data['future']:
result = result + "\n" + f"Date: {day['date']}, Weather: {day['weather']}, Temperature: {day['temperature']}, Wind Direction: {day['direct']}"
return result
```
After starting the bot, you can @ it in the group chat with "/weather Haikou" or other place names.
# III. You've Made It This Far
Why not join our QQ group (710101225) to chat and vibe with the code wizards and group homies? (doge)
If you liked this, we'd really appreciate a star on our [repository](https://github.com/ClovertaTheTrilobita/SanYeCao-Nonebot).

View file

@ -1,24 +1,153 @@
--- ---
title: 'My Second Blog Post' title: "(Tutorial) Installing Sunshine on Raspberry Pi 5 for LAN Streaming"
pubDate: 2022-07-01 pubDate: 2025-04-21
description: 'This is the first post of my new Astro blog.' description: 'Installing Sunshine on a Raspberry Pi'
author: 'Astro Learner' author: "Cloverta"
image: image:
url: 'https://files.seeusercontent.com/2026/03/25/0rSi/rikka-manga.jpeg' url: "https://files.seeusercontent.com/2026/03/25/t2zJ/pasted-image-1774456500701.webp"
alt: 'The Astro logo on a dark background with a pink glow.' alt: "img"
tags: ["astro", "blogging", "learning in public"] tags: ["Raspberry Pi", "Linux", "Tutorial"]
--- ---
Welcome to my _new blog_ about learning Astro! Here, I will share my learning journey as I build a new website. <p style="font-size: 0.85rem;"><i><sub>Content translated by <a href="https://www.deepseek.com/">DeepSeek</a>.</sub></i></p>
## What I've accomplished ## 0. Before We Start
1. **Installing Astro**: First, I created a new Astro project and set up my online accounts. What we need to understand is that the Raspberry Pi 5 uses an arm64 architecture processor, so we need to make sure we get the correct Sunshine version for it.
2. **Making Pages**: I then learned how to make pages by creating new `.astro` files and placing them in the `src/pages/` folder. Also, configuring Sunshine in a Linux environment can be a bit of a hassle, but it shouldn't be too bad (probably).
3. **Making Blog Posts**: This is my first blog post! I now have Astro pages and Markdown posts! Let's get started.
## What's next ## 1. Installing Sunshine
I will finish the Astro tutorial, and then keep adding more posts. Watch this space for more to come. Link: [LizardByte: Sunshine for Linux](https://github.com/LizardByte/Sunshine/releases)
The Raspberry Pi uses a Debian-based operating system, so we need to find **sunshine-debian-bookworm-arm64.deb**. Click to download.
![pasted-image-1774456393003.webp](https://files.seeusercontent.com/2026/03/25/r9Ot/pasted-image-1774456393003.webp)
Transfer the downloaded .deb installation package to your Raspberry Pi (or just download it directly on the Pi).
Navigate to the directory containing the package and enter the following in the terminal:
```shell
dpkg -i sunshine-debian-bookworm-arm64.deb
```
to manually install Sunshine. Use the actual filename of your package.
If you encounter missing dependency issues, enter:
```shell
sudo apt-get install -f
```
to install the missing dependencies. After installing the dependencies, try installing the Sunshine .deb package again.
In the terminal, enter:
```shell
sunshine
```
and press Enter to check if the installation was successful.
**Note:** At this point, Sunshine will most likely fail to start because necessary configurations are missing.
So, let's move on to the next step.
## 2. Configuring the Raspberry Pi
### ① Update the System
Ensure your Raspberry Pi's software is up-to-date by running:
```shell
sudo apt update && sudo apt full-upgrade -y
```
This step is necessary because Wayland support might depend on system updates.
### ② Enable Wayland Support
In newer versions of Raspberry Pi OS, Wayland is enabled by default. However, if you encounter an error when using Sunshine like:
> Error: Environment variable WAYLAND_DISPLAY has not been defined
Then first, modify the boot configuration:
```shell
sudo nano /boot/firmware/config.txt
```
to open the boot configuration file. Add the following two lines at the end:
```shell
dtoverlay=vc4-fkms-v3d
max_framebuffers=2
```
to enable hardware acceleration. Save, exit, and reboot the device.
Afterwards, enter:
```shell
sudo raspi-config
```
to enter the Raspberry Pi system configuration. Navigate sequentially to:
> 6 Advanced Options
>
> A6 Wayland
>
> W3 Labwc (This is the recommended Wayland compositor for Raspberry Pi)
After configuration, reboot.
Open the VNC remote desktop (**make sure not to use an SSH terminal for this**). In the terminal, enter:
```shell
echo $XDG_SESSION_TYPE
```
If the output is `wayland`, the setup was successful.
### ③ Enable avahi-daemon
If you encounter an error:
> Error: Failed to create client: Daemon not running
Enter the following in the terminal:
```shell
systemctl enable avahi-daemon
```
## 3. Starting Sunshine
Excellent! All configurations should now be complete!
In the terminal of your VNC remote desktop, enter:
```shell
sunshine
```
to start Sunshine.
**Note:** If you don't have a virtual display installed, you need to ensure the Raspberry Pi has at least one desktop session active, whether via VNC or connected to a physical monitor. It's best not to start Sunshine via an SSH terminal.
If successful, you should see something like this in the terminal:
![pasted-image-1774456462002.webp](https://files.seeusercontent.com/2026/03/25/Cn4m/pasted-image-1774456462002.webp)
Ctrl + Left-click on `https://localhost:47990` to access the Sunshine Web UI through your browser. You should see the familiar interface.
![pasted-image-1774456481966.webp](https://files.seeusercontent.com/2026/03/25/Xk6z/pasted-image-1774456481966.webp)
The subsequent steps for connecting are the same as for the Windows version of Sunshine.
![pasted-image-1774456500701.webp](https://files.seeusercontent.com/2026/03/25/t2zJ/pasted-image-1774456500701.webp)

View file

@ -9,6 +9,8 @@ image:
tags: ["Peterson's algorithm", "Operating System"] tags: ["Peterson's algorithm", "Operating System"]
--- ---
<p style="font-size: 0.85rem;"><i><sub>Content translated by <a href="https://chatgpt.com/">ChatGPT</a>></sub></i></p>
In my OS course, I came across an interesting algorithm — Peterson's Algorithm. So why does Peterson's Algorithm satisfy the three conditions: "mutual exclusion", "progress", and "bounded waiting"? In my OS course, I came across an interesting algorithm — Peterson's Algorithm. So why does Peterson's Algorithm satisfy the three conditions: "mutual exclusion", "progress", and "bounded waiting"?
First, here is the pseudocode: First, here is the pseudocode:

129
src/blog/en/post-4.md Normal file
View file

@ -0,0 +1,129 @@
---
title: "[Study Notes] The Classic Readers-Writers Problem"
pubDate: 2025-06-13
description: 'Study notes on the Readers-Writers algorithm'
author: "Cloverta"
image:
url: "https://files.seeusercontent.com/2026/03/25/cd8O/pasted-image-1774458552123.webp"
alt: "img"
tags: ["Algorithms", "Operating Systems", "Study Notes"]
---
<p style="font-size: 0.85rem;"><i><sub>Content translated by <a href="https://www.deepseek.com/">DeepSeek</a>.</sub></i></p>
## First, let's understand the concepts of Readers and Writers
In computing, there are two concurrent processes: **Readers** and **Writers**, sharing a single file.
Multiple reader processes are allowed to access the shared data simultaneously, but a writer process is not allowed to operate concurrently with any other process.
**Therefore, the requirements are:**
1⃣ Allow multiple readers to perform read operations on the file concurrently.
2⃣ Allow only one writer to write information to the file at a time.
3⃣ Any writer must not allow other readers or writers to work until it completes its write operation.
4⃣ Before a writer performs a write operation, all existing readers and writers must exit.
In other words, **reader processes can be concurrent with other reader processes, while writer processes are mutually exclusive with both reader and writer processes**.
## How can we implement this?
## One Possible Approach
We can easily think of placing a read-write lock on the buffer. When a writer process starts writing data to the buffer, it acquires the lock, preventing other processes from accessing the buffer.
But how do we achieve concurrent reading for reader processes?
We set up a semaphore `count`. Each reader process increments `count` when it starts reading and decrements it when it finishes.
Then it becomes simple. The first reader process acquires the lock when it starts reading, and the last reader process releases the lock when it finishes.
Let's look at the code.
```c++
semaphore rw = 1; // Read-write lock for mutual exclusion of shared process access
int count = 0; // Records how many reader processes are accessing the file
semaphore mutex = 1; // Lock for 'count' to prevent two readers accessing count simultaneously, which could cause a deadlock
writer () {
while(1){
P(rw); // Lock the buffer (P operation, rw--)
Write to file;
V(rw); // Unlock the buffer (V operation, rw++)
}
}
reader () {
while(1){
P(mutex); // Lock 'count' to prevent two readers simultaneously reading count==0, which could cause a deadlock.
if (count == 0){
P(rw); // If count is 0, check if the buffer is locked. If locked, wait. If unlocked, lock the buffer.
}
count++;
V(mutex); // Unlock 'count'
Read file;
P(mutex); // Lock 'count' again to prevent two processes simultaneously reading count==0, which could cause rw to be incremented twice abnormally.
count--;
if (count == 0){
V(rw); // If count is 0, this process is the last reader, so unlock the buffer.
}
V(mutex);
}
}
```
**However, we will find a problem with this approach.**
**What if reader processes keep arriving?**
If reader processes keep requesting to read the buffer (which is very common in operating systems), then `count` will never be 0. This will cause writer processes to be stuck waiting indefinitely, leading to **starvation**. **This algorithm gives reader processes the highest priority.**
### Another Approach for Fairness
Since writers cannot write if the reader queue is not empty, let's try this:
When a writer process requests buffer resources, **block all reader processes that arrive *after* this writer process** from entering the reader queue.
Wait until the existing reader queue is exhausted. After the writer finishes writing data to the buffer, then allow the later reader processes to enter.
Let's look at the code:
```c++
semaphore rw = 1;
int count = 0;
semaphore mutex = 1;
semaphore w = 1; // Used to implement writer-priority
writer () {
while (1) {
P(w); // Writer first locks 'w'. After locking, subsequent reader processes must wait for this writer to unlock.
P(rw); // Lock the buffer
Write to file;
V(rw);
V(w); // Unlock, allowing reader processes to join the queue.
}
}
reader () {
while (1) {
P(w); // When a new reader joins, first check if the 'w' lock is held. If locked, block and wait. If unlocked, lock it first.
P(mutex);
if (count == 0)
P(rw);
count++;
V(mutex);
V(w); // After the reader joins the queue, unlock the 'w' lock, ensuring the atomicity of the join operation.
Read file;
P(mutex);
count--;
if (count == 0)
V(rw);
V(mutex);
}
}
```
By adding a new lock `w`, the priority of writer processes is significantly increased, implementing a **first-come, first-served** fairness.

116
src/blog/en/post-5.md Normal file
View file

@ -0,0 +1,116 @@
---
title: "[Study Notes] How the Banker's Algorithm Prevents Deadlock"
pubDate: 2025-06-14
description: "Study notes on the Banker's Algorithm"
author: "Cloverta"
image:
url: "https://files.seeusercontent.com/2026/03/25/wf4H/pasted-image-1774458677434.webp"
alt: "img"
tags: ["Algorithms", "Operating Systems", "Study Notes"]
---
<p style="font-size: 0.85rem;"><i><sub>Content translated by <a href="https://www.deepseek.com/">DeepSeek</a>.</sub></i></p>
The Banker's Algorithm was first proposed by the computer science legend Edsger Dijkstra, aiming to solve the problem where a bank sometimes cannot lend money rationally.
Its core idea is that **the system predicts a resource allocation strategy before actually allocating resources, to avoid situations where allocated resources are less than the remaining resource demands.**
Before we start, we need to introduce a concept: the Safe Sequence.
## Safe Sequence
A safe sequence refers to a sequence of processes such that if the system allocates resources according to this sequence, each process can complete successfully.
This is the "predicted resource allocation strategy" mentioned earlier.
As long as we can find one safe sequence, the system is in a **safe state**. Conversely, if after a resource allocation, the system cannot find any safe sequence, then the system enters an **unsafe state**. A system in an unsafe state **might experience deadlock**.
Therefore, we need to predict whether a resource allocation request would lead the system into an unsafe state before granting it, to decide whether to approve the request.
So, how do we calculate this safe sequence?
## The Banker's Algorithm
Let's take an example
> > Assume the system has 5 processes P0P4 and 3 types of resources R0R2. The initial quantities of these resources are (10, 5, 7). At a certain moment, the situation is:
| Process | Max Need (Max) | Allocated (Allocation) | Still Needs (Need) |
| ------------- | -------------- | ---------------------- | ------------------ |
| P0 | (7, 5, 3) | (0, 1, 0) | (7, 4, 3) |
| P1 | (3, 2, 2) | (2, 0, 0) | (1, 2, 2) |
| P2 | (9, 0, 2) | (3, 0, 2) | (6, 0, 0) |
| P3 | (2, 2, 2) | (2, 1, 1) | (0, 1, 1) |
| P4 | (4, 3, 3) | (0, 0, 2) | (4, 3, 1) |
In what order should we allocate resources to these processes to prevent deadlock?
First, we can calculate that at this moment, the remaining resources in the system are:
```
(10, 5, 7) - (0, 1, 0) - (2, 0, 0) - (3, 0, 2) - (2, 1, 1) - (0, 0, 2) = (3, 3, 2)
```
Next, we compare sequentially.
Clearly, allocating to P0 is not feasible. Its maximum required resources exceed the available resources. If we allocate all available resources to it, P0 still cannot complete its work, and no other process would get any resources.
Let's look at P1. The resources P1 needs (1, 2, 2) are less than the system's available resources (3, 3, 2). Therefore, the system can allocate resources to P1. Since P1 obtains all the resources it needs, it can definitely complete its task smoothly and release the resources.
So, if we allocate resources to P1, after P1 finishes running, the resources we can control become:
```
(3, 3, 2) + (2, 0, 0) = (5, 3, 2)
```
Following this logic, we continue searching.
P2 needs some resources that exceed what we hold, so we skip it for now.
P3 needs resources less than what we hold, so we can allocate resources to P3. After P3 finishes, the system reclaims the resources held by P3. At this point, the free resources become:
```
(5, 3, 2) + (2, 1, 1) = (7, 4, 3)
```
Now, the system's free resources are all greater than or equal to what the remaining processes need. We simply need to allocate resources to P0, P2, and P4 in sequence and reclaim them afterwards.
Thus, we have successfully obtained a safe sequence:
```mermaid
%% This is actually a flowchart!!
%% If you see this comment, it means the flowchart plugin might be glitching. Refreshing the page should fix it QwQ
graph LR;
P1[P1 ]:::rose --> P3[P3 ]:::rose
P3 --> P0[P0 ]:::rose
P0 --> P2[P2 ]:::rose
P2 --> P4[P4 ]:::rose
classDef rose fill:#C98986,stroke:#C98986,color:black;
classDef blue fill:#82A7A6,stroke:#82A7A6,color:black
classDef taupe fill:#785964,stroke:#785964,color:white
classDef note fill:#F4F4F4,stroke:#F4F4F4,color:black
classDef beaver fill:#9A8873,stroke:#9A8873,color:white
```
Of course, if you want to change it to:
```mermaid
%% This is actually a flowchart!!
%% If you see this comment, it means the flowchart plugin might be glitching. Refreshing the page should fix it QwQ
graph LR;
P1[P1 ]:::rose --> P3[P3 ]:::rose
P3 --> P2[P2 ]:::rose
P2 --> P0[P0 ]:::rose
P0 --> P4[P4 ]:::rose
classDef rose fill:#C98986,stroke:#C98986,color:black;
classDef blue fill:#82A7A6,stroke:#82A7A6,color:black
classDef taupe fill:#785964,stroke:#785964,color:white
classDef note fill:#F4F4F4,stroke:#F4F4F4,color:black
classDef beaver fill:#9A8873,stroke:#9A8873,color:white
```
Or any other order, it doesn't matter, because **there can be multiple safe sequences**.

140
src/blog/en/post-6.md Normal file
View file

@ -0,0 +1,140 @@
---
title: "[Study Notes] Implementation Principles of Paged Memory Management"
pubDate: 2025-06-16
description: 'Study notes and some thoughts on paged memory management'
author: "Cloverta"
image:
url: "https://files.seeusercontent.com/2026/03/25/n5uP/pasted-image-1774459065133.webp"
alt: "img"
tags: ["Operating Systems", "Study Notes"]
---
<p style="font-size: 0.85rem;"><i><sub>Content translated by <a href="https://www.deepseek.com/">DeepSeek</a>.</sub></i></p>
We know that early operating systems used methods like partition allocation to manage memory, but these methods had a common problem: they couldn't fully utilize memory space, easily creating a large number of tiny, hard-to-use memory fragments, and performance was poor.
So, people came up with a better memory management scheme, namely:
# Paged Memory Management
How is paged memory management implemented?
First, we divide the physical memory into partitions of equal size, called **frames (or memory blocks)**. Each frame has a **frame number (or memory block number)**. Frame numbers **start from 0**.
We know that processes use logical addresses when running (which is more efficient). Therefore, we can also **divide the logical address space of a process into parts of equal size to the frames**.
The partitions divided from the logical address are called **pages**. Each page is numbered with a **page number**. Page numbers also **start from 0**.
Pages and frames have a one-to-one correspondence.
To know which memory block each page corresponds to, the operating system also needs to create a **page table**.
> 1. Each process corresponds to one page table.
>
> 2. Each page of the process corresponds to one page table entry.
>
> 3. Each **page table entry** consists of a page number and a block number.
>
> 4. The page table records the **mapping relationship** between a process's **pages** and the actual **memory blocks** where they are stored.
**Note:** What is stored in the page table is the **memory block number**, not the starting address of the memory block.
Let's summarize.
- **Frame:** A block divided in physical memory (equal size).
- **Page:** A block divided from the logical address (corresponds one-to-one with frames in memory, also equal size).
- **Page Table:** Used to record the location of pages in physical memory. (The page table stores the **number** of the frame/memory block, not the address.)
## Address Translation
At this point, if the operating system wants to access a logical address A in a process, it needs to:
1⃣ Determine the page number P corresponding to logical address A.
2⃣ Query the page table to find the starting address of page P in memory.
3⃣ Determine the page offset W within logical address A.
In other words:
Physical address corresponding to logical address A = Starting address of page P in memory + Page offset W
> Page number = Logical address / Page size
> Page offset = Logical address % Page size
At this point, the operating system should be able to use memory more efficiently. But, boss, you've added a page table as an intermediary. The method of finding the actual address by multiplying the memory block number stored in the page table by the block size is efficient, but is there a method that's even more efficient and less time-complexity heavy?
Yes, bro, yes. It's...
## Basic Address Translation Mechanism
In more modern operating systems, a **Page Table Register (PTR)** is usually set up. In the PTR, we store the **starting address F** of the page table in memory and the **page table length M**. (The page table is stored contiguously in memory.)
When a process is not executing, **its starting address and length are stored in its PCB**.
When the process is scheduled, the kernel loads them into the page table register.
Here's the flowchart:
```mermaid
%% This is actually a flowchart!!
%% If you see this comment, it means the flowchart plugin might be glitching. Refreshing the page should fix it QwQ
graph TD;
A[Page Number P.]:::cadet --> B[Is it less than Page Table Length?]:::blue
B -->|Yes| C[Page Fault / Interrupt.]:::taupe
B -->|No| D[Page Table Start F + P * Page Table Entry Length.]:::rose
D -->|Find| E[Page Table Entry for Page P.]:::rose
E -->|Find| F[Memory Block Number.]:::rose
F -->|Memory Block Number * Block Size.| G[Physical Address E.]:::cadet
classDef rose fill:#C98986,stroke:#C98986,color:black;
classDef blue fill:#82A7A6,stroke:#82A7A6,color:black
classDef taupe fill:#785964,stroke:#785964,color:white
classDef note fill:#F4F4F4,stroke:#F4F4F4,color:black
classDef beaver fill:#9A8873,stroke:#9A8873,color:white
classDef silver fill:#C4BBB8, stroke:#C4BBB8
classDef cadet fill:#39375B,stroke:#39375B,color:white
```
In other words, when the operating system wants to access an address, it needs to:
1⃣ Find the page number P from the logical address A and check if P is less than the total length of the page table.
2⃣ Find the page table start address F from the page table register PTR, and use **page number P \* page table entry length M + start address F** to get the **page table entry** corresponding to page P.
3⃣ We know the page table entry stores the **memory block number**. Now we just need to multiply the memory block number by the block size to find the physical address E 🎉
> **Note:**
>
> - **Page table length** is the total number of entries in the page table.
>
> - **Page table entry length** is the **storage space size** occupied by a single page table entry.
Looks simple? This method **of storing the page table location in a register, calculating the location of the page table entry in memory to retrieve the memory block number, and then calculating the physical address from the block number** is happening in computers worldwide, and you could be the next one. Unless...
You make the most important decision of your life! That is, adopt the continuous allocation management method!
Allocate contiguous memory space for user processes! Use partition allocation with fitting algorithms!
Become a master of fragment cleanup.
Now let's take an example to solidify our understanding 🌰
> If the page size L is 1K bytes, the memory block number b corresponding to page number 2 is 8, convert the logical address A=2500 to a physical address E.
In plain English: In the operating system, a page/frame size is 1K, i.e., 1024 bytes. In the page table, the page table entry for page number 2 stores memory block number 8. Now we need to convert the byte at logical address 2500 to a physical address.
1⃣ Calculate the page number and page offset (i.e., the distance of this address from the start of the page within a single page).
**Page number P = A / L = 2500 / 1024 = 2**, meaning A is within the second page in the logical address space.
2⃣ Find the memory block corresponding to this page and calculate the physical address.
It was mentioned earlier that page number 2 corresponds to memory block number 8. So we can calculate the actual starting address of this page as:
**b \* L = 8 \* 1024 = 8192**
Then the physical address E is the memory block address plus the offset:
**E = b \* L + W = 8 \* 1024 + 452 = 8644**

784
src/blog/en/post-7.md Normal file
View file

@ -0,0 +1,784 @@
---
title: "(Tutorial) Installing NixOS Dual Boot Alongside Existing Windows"
pubDate: 2025-08-01
description: 'A beginner-friendly (hopefully) NixOS dual-boot installation tutorial.'
author: "Cloverta"
image:
url: "https://files.seeusercontent.com/2026/03/25/zhK7/pasted-image-1774459137516.webp"
alt: "baka"
tags: ["Operating Systems", "NixOS"]
---
<p style="font-size: 0.85rem;"><i><sub>Content translated by <a href="https://www.deepseek.com/">DeepSeek</a>.</sub></i></p>
# Installing NixOS Dual Boot Alongside Existing Windows
I looked for existing NixOS installation tutorials online, but many are outdated/unusable.
So, I thought about integrating the blogs I previously referenced to create a beginner-friendly NixOS dual-boot installation tutorial.
## 1. Preparations Before Starting
Before installing NixOS, we need to make some settings adjustments to the existing Windows installation on the computer.
### Disable Fast Startup
If Windows Fast Startup is not disabled, it might cause NixOS to lose network connectivity.
Open Control Panel, navigate to:
```
Control Panel\Hardware and Sound\Power Options\System Settings
```
Find **Shutdown settings > Turn on fast startup (recommended)** and uncheck it.
![pasted-image-1774459292989.webp](https://files.seeusercontent.com/2026/03/25/pXg9/pasted-image-1774459292989.webp)
### Free Up Disk Space for NixOS
You can use software like DiskGenius, Partition Assistant, etc., to manage the free space on your hard drive. For daily use, it's recommended to allocate at least 200GB of hard drive space for NixOS.
![pasted-image-1774459307930.webp](https://files.seeusercontent.com/2026/03/25/6xIx/pasted-image-1774459307930.webp)
### Create Bootable Media
It's recommended to use [Rufus](https://rufus.ie/zh/) to create a bootable USB drive. It's very simple and easy to use. Of course, other software like [Ventoy](https://www.ventoy.net/cn/download.html) will also work.
First, download the official ISO from the [NixOS website](https://nixos.org/download/).
It's recommended to use the Minimal ISO image, as the graphical installer can introduce many complications.
![pasted-image-1774459311524.webp](https://files.seeusercontent.com/2026/03/25/Wgc9/pasted-image-1774459311524.webp)
Choose the appropriate type based on your CPU. For example, if you have an x86 architecture CPU (Intel or AMD), choose the Intel/AMD option above.
After downloading, insert a USB drive (at least 2GB in size) and open Rufus.
- Device: Select the USB drive you just inserted.
- Boot selection: Choose the ISO file you just downloaded.
- Partition scheme: Select GPT.
- Target system type: Select UEFI.
![pasted-image-1774459315299.webp](https://files.seeusercontent.com/2026/03/25/lZv1/pasted-image-1774459315299.webp)
Click **Start**. In the pop-up window, select **Write in ISO image mode (Recommended)**.
![pasted-image-1774459318819.webp](https://files.seeusercontent.com/2026/03/25/l2Hy/pasted-image-1774459318819.webp)
Click **OK**.
Wait a moment, sit back and relax.
Once the writing is complete, we can proceed to the next step.
## 2. Adjust Boot Order
### Enter Computer BIOS
Restart your computer and press `F2` (or the key for your manufacturer) when the manufacturer's logo appears on the screen to enter the BIOS.
Of course, the BIOS key and interface vary by manufacturer, so you'll need to look up the specifics for your device.
### Disable Secure Boot
Here's an image borrowed from another blog.
![pasted-image-1774459322972.webp](https://files.seeusercontent.com/2026/03/25/ys4V/pasted-image-1774459322972.webp)
Enter the BIOS Advanced settings (the name may vary). Find **Security > Secure Boot** and disable it.
Save and restart the computer. Usually, the computer will restart automatically after saving.
Press the key again at the manufacturer's logo to enter the BIOS. If your bootable USB is properly inserted, you should see a corresponding boot option like **UEFI: USB, Partition 1 (USB)**. Set its priority to the highest, i.e., drag it to the top.
![pasted-image-1774459326764.webp](https://files.seeusercontent.com/2026/03/25/j9Kz/pasted-image-1774459326764.webp)
Since I already have a NixOS installed on my computer, please ignore the top NixOS-boot entry. If your computer hasn't installed other systems, you should only see `Windows Boot Manager` and the `UEFI: USB` entry below.
## Enter Live CD Installation Mode
First, you'll enter a NixOS boot menu, roughly asking you to choose what type of NixOS to install. We'll choose the default first option. Then, you'll see a CLI interface like this:
![pasted-image-1774459330911.webp](https://files.seeusercontent.com/2026/03/25/8eUs/pasted-image-1774459330911.webp)
Similarly, the image above is borrowed from someone else (because I'm lazy). We are installing NixOS 25.05, so the version number is different from the ancient image above, which is normal.
Since we chose the minimal installation, there is no clickable UI. The interface we are currently in is the famous `tty`.
Note: The current system is running entirely from the USB drive. What we need to do next is install it onto the computer's hard drive.
### Enable WiFi
The first thing we need to do is—connect to the network.
If you have an Ethernet cable connected to your computer, you can skip this step.
Start the `wpa_supplicant` service:
```shell
sudo systemctl start wpa_supplicant
```
Enter interactive mode:
```shell
sudo wpa_cli
```
In the command line, enter sequentially:
```shell
> add_network
0
> set_network 0 ssid "Your WiFi Name"
OK
> set_network 0 psk "WiFi Password"
OK
> set_network 0 key_mgmt WPA-PSK
OK
> enable_network 0
OK
```
If you see output similar to:
```shell
<3>CTRL-EVENT-CONNECTED - Connection to 32:85:ab:ef:24:5c completed [id=0 id_str=]
```
And there are continuous send/receive packet logs output in the terminal, it means the connection is successful.
You can now type `quit` and press Enter to exit.
If you're not sure, you can ping this blog:
```shell
ping blog.cloverta.top
```
If you receive most or all packets, the connection is normal.
### Switch to a Domestic Mirror Source
As we all know, due to certain reasons, we may not be able to connect to NixOS's official sources reliably.
```shell
sudo -i
nix-channel --add https://mirrors.ustc.edu.cn/nix-channels/nixos-unstable nixos
nix-channel --update # Update and unpack the channel
```
Add the University of Electronic Science and Technology of China source.
## Partitioning
Remember the disk space we freed up for NixOS earlier? Now we need to make good use of it.
Enter the command:
```shell
lsblk
```
You should get output similar to this:
```shell
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 1 57.3G 0 disk
└─sda1 8:1 1 57.3G 0 part /run/media/cloverta/NIXOS-MINIM
nvme0n1 259:0 0 953.9G 0 disk
├─nvme0n1p1 259:1 0 113.1G 0 part
└─nvme0n1p2 259:2 0 512G 0 part
```
Here, `sda` corresponds to your USB drive, `nvme0n1` corresponds to your computer's SSD, and `nvme0n1p1`, `nvme0n1p2` are the existing disk partitions on your computer.
If you have multiple hard drives installed, there might also be `nvme1n1`, etc.
**Make absolutely sure which hard drive you are operating on!! Don't accidentally delete your Windows C drive (**
If you performed partitioning on `nvme0n1` (clearly, the size of the two partitions in the above information is much smaller than the total hard drive size), then enter:
```shell
cfdisk /dev/nvme0n1
```
We will see an interface similar to the following:
![pasted-image-1774459338907.webp](https://files.seeusercontent.com/2026/03/25/tE7b/pasted-image-1774459338907.webp)
Of course, what you see will be in English.
The selection box at the bottom of your interface should have options like:
```
[ Delete ] [ New ] [ Quit ] [ Help ] [ Write ] [ Dump ]
```
First, select the free disk space, choose New to create a partition, and manually enter the size.
We need to create two partitions: one main partition and another boot partition. The boot partition is recommended to be 512MB.
Set the type of both partitions to `Primary`. After creating the partitions, enter `lsblk` again to check the partition status. You should see these two new partitions:
```shell
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 1 57.3G 0 disk
└─sda1 8:1 1 57.3G 0 part /run/media/cloverta/NIXOS-MINIM
nvme0n1 259:0 0 953.9G 0 disk
├─nvme0n1p1 259:1 0 113.1G 0 part
├─nvme0n1p2 259:2 0 512G 0 part
├─nvme0n1p3 259:3 0 0.5G 0 part
└─nvme0n1p4 259:4 0 327.8G 0 part
```
Next, we need to format these two new partitions. **Pay attention to the partition numbers!**
For example, the two new partitions above are `nvme0n1p3` and `nvme0n1p4`. But if your new partitions are `nvme0n1p5`, you need to change the corresponding partition numbers in subsequent operations. Don't get it wrong (
Let's take the above partition numbers as an example.
We can see that `nvme0n1p3` is planned to be used as the boot partition. Let's format it to FAT32:
```shell
mkfs.fat -F 32 -n boot /dev/nvme0n1p3
```
Then, format the main partition to btrfs format:
```shell
mkfs.btrfs -L nixos /dev/nvme0n1p4
```
Then mount these two partitions to the NixOS filesystem:
```shell
mount /dev/nvme0n1p4 /mnt
mkdir -p /mnt/boot
mount /dev/nvme0n1p3 /mnt/boot
```
Now check if the mounting was successful:
```shell
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 1 57.3G 0 disk
└─sda1 8:1 1 57.3G 0 part /run/media/cloverta/NIXOS-MINIM
nvme0n1 259:0 0 953.9G 0 disk
├─nvme0n1p1 259:1 0 113.1G 0 part
├─nvme0n1p2 259:2 0 512G 0 part
├─nvme0n1p3 259:3 0 0.5G 0 part /boot
└─nvme0n1p4 259:4 0 327.8G 0 part /nix/store
```
## Edit System Configuration
You must have heard of Nix's famous declarative system configuration method, so I won't elaborate much here.
NixOS saves all system configurations in a file called `configuration.nix`. Any changes to the system, management, software installation, etc., you only need to modify this `configuration.nix`! You don't need to manually configure complex dependencies and headaches caused by conflicts. NixOS will handle everything for you!
If something goes wrong, you can always revert `configuration.nix` back, fundamentally avoiding a broken system (looking at you, Arch).
Now, let's generate a default system configuration file:
```shell
nixos-generate-config --root /mnt
```
Then edit the configuration:
```shell
vim /mnt/etc/nixos/configuration.nix
```
You should see a configuration file similar to this:
```nix
{ config, lib, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
# Use the systemd-boot EFI boot loader.
# boot.loader.systemd-boot.enable = true;
# boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "nixos"; # Define your hostname.
# Pick only one of the below networking options.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
# networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
# Set your time zone.
# time.timeZone = "";
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Select internationalisation properties.
i18n = "en_US.UTF-8";
# Enable the X11 windowing system.
# services.xserver.enable = true;
# Configure keymap in X11
# services.xserver.xkb.layout = "us";
# services.xserver.xkb.options = "eurosign:e,caps:escape";
# Enable CUPS to print documents.
# services.printing.enable = true;
# Enable sound.
# services.pulseaudio.enable = true;
# OR
# services.pipewire = {
# enable = true;
# pulse.enable = true;
# };
# Enable touchpad support (enabled default in most desktopManager).
# services.libinput.enable = true;
# Define a user account. Don't forget to set a password with passwd.
# users.users.Alice = {
# isNormalUser = true;
# extraGroups = [ "wheel" ]; # Enable sudo for the user.
# packages = with pkgs; [
# tree
# ];
# };
# programs.firefox.enable = true;
# List packages installed in system profile.
# You can use https://search.nixos.org/ to find more packages (and options).
environment.systemPackages = with pkgs;
[
vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
];
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
# programs.gnupg.agent = {
# enable = true;
# enableSSHSupport = true;
# };
# List services that you want to enable:
# Enable the OpenSSH daemon.
# services.openssh.enable = true;
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
# Copy the NixOS configuration file and link it from the resulting system
# (/run/current-system/configuration.nix). This is useful in case you
# accidentally delete configuration.nix.
# system.copySystemConfiguration = true;
# This option defines the first version of NixOS you have installed on this particular machine,
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
#
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
#
# This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
# so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how
# to actually do that.
#
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
#
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
#
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "25.05"; # Did you read the comment?
}
```
This is the most minimal NixOS configuration file. Now let's install a desktop environment:
### Basic Configuration
#### 0. Grub for Dual Boot
Find the following section in the configuration file:
```
# Use the systemd-boot EFI boot loader.
# boot.loader.systemd-boot.enable = true;
# boot.loader.efi.canTouchEfiVariables = true;
```
We'll add our own grub configuration:
```
# Use the systemd-boot EFI boot loader.
# boot.loader.systemd-boot.enable = true;
boot.loader = {
efi = {
canTouchEfiVariables = true;
efiSysMountPoint = "/boot";
};
grub = {
enable = true;
device = "nodev";
useOSProber = true;
efiSupport = true;
};
};
```
#### 1. Configure Language
Find the following in the configuration file:
```
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
```
If it's not `en_US.UTF-8`, set it to English first.
#### 2. Configure Network
Find:
```
# Pick only one of the below networking options.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
# networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
```
Here we'll use networkmanager (easier to use), so uncomment the second line:
```
# Pick only one of the below networking options.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
```
#### 3. Configure Time Zone
Find:
```
# Set your time zone.
# time.timeZone = "";
```
Change it to:
```
# Set your time zone.
time.timeZone = "Asia/Shanghai"
```
#### 4. Configure Sound
Find:
```
# Enable sound.
# services.pulseaudio.enable = true;
# OR
# services.pipewire = {
# enable = true;
# pulse.enable = true;
# };
```
We'll use pipewire:
```
# Enable sound.
# services.pulseaudio.enable = true;
# OR
services.pipewire = {
enable = true;
pulse.enable = true;
};
```
#### 5. Configure User
```
# Define a user account. Don't forget to set a password with passwd.
# users.users.alice = {
# isNormalUser = true;
# extraGroups = [ "wheel" ]; # Enable sudo for the user.
# packages = with pkgs; [
# tree
# ];
# };
```
Uncomment the above section and configure your username.
For example, I want my username to be cloverta, so:
```
# Define a user account. Don't forget to set a password with passwd.
users.users.cloverta = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable sudo for the user.
packages = with pkgs; [
tree
];
};
```
#### 6. Enable Firefox
```
programs.firefox.enable = true;
```
#### 7. Enable SSH
```
# Enable the OpenSSH daemon.
# services.openssh.enable = true;
```
Uncomment the second line:
```
# Enable the OpenSSH daemon.
services.openssh.enable = true;
```
#### 8. Configure Mirror Source
Add the following to the configuration file:
```
nix.settings.substituters = [ "https://mirror.sjtu.edu.cn/nix-channels/store" ];
```
You can also look at what's in the default configuration file and enable some small features according to your preferences, but
#### Caution!!
**Do NOT change the value of the last line `system.stateVersion = "25.05";`. If you must change it, be prepared to face the consequences.**
Now let's choose from two classic graphical desktops:
### GNOME
Add the following to the configuration file:
```
services.displayManager.gdm.enable = true;
services.desktopManager.gnome.enable = true;
```
And in the block:
```
environment.systemPackages = with pkgs;
[
vim
]
```
Add:
```
gnomeExtensions.blur-my-shell
gnomeExtensions.just-perfection
gnomeExtensions.arc-menu
```
These are some useful GNOME extensions.
### KDE Plasma
Do not add the above content. Instead, add the following to the configuration file:
```
services.xserver.enable = true; # optional
services.displayManager.sddm.enable = true;
services.displayManager.sddm.wayland.enable = true;
services.desktopManager.plasma6.enable = true;
```
And add the following to `environment.systemPackages = with pkgs;[]`:
```
wget
kitty
kdePackages.kcalc
kdePackages.kcharselect
kdePackages.kcolorchooser
kdePackages.kolourpaint
kdePackages.ksystemlog
kdePackages.sddm-kcm
kdiff3
kdePackages.isoimagewriter
kdePackages.partitionmanager
hardinfo2
haruna
wayland-utils
```
## Deploy the System
Finally! We've completed the configuration! Yay (ノ>ω<)ノ
Now install the system:
```
sudo nixos-install --option substituters "https://mirror.sjtu.edu.cn/nix-channels/store"
```
Afterwards, set the password:
Note: My username is cloverta. You need to change it to the username you set in the configuration file.
```
nixos-enter # Enter the deployed system, similar to arch's chroot
passwd root # Reset root password
useradd -m -G wheel cloverta # Add a regular user and add to the wheel group
passwd cloverta # Set the regular account password
```
Then shut down. You'll likely still need to go into the BIOS to adjust the boot order. It's recommended to move the NixOS boot entry to the top, as you'll be able to choose between booting into NixOS or Windows from the GRUB menu.
Now! Witness the miracle! The screen lights up! キタ━━━━(゚∀゚)━━━━!!
## Some Post-Installation Configuration
Now that you have a desktop environment, copying and pasting to modify the configuration file is much more convenient.
I won't elaborate on the rest. After modifying `configuration.nix`, use:
```
sudo nixos-rebuild switch
```
to apply the latest changes.
### Chinese Input Method
We'll install fcitx5. Find the `i18n` variable we modified earlier and change it to:
```
i18n = {
defaultLocale = "zh_CN.UTF-8";
supportedLocales = [ "zh_CN.UTF-8/UTF-8" "en_US.UTF-8/UTF-8" ];
# Fcitx5
inputMethod = {
type = "fcitx5";
enable = true;
fcitx5 = {
waylandFrontend = true;
plasma6Support = true;
};
fcitx5.addons = with pkgs; [
fcitx5-chinese-addons
fcitx5-pinyin-moegirl
fcitx5-pinyin-zhwiki
fcitx5-configtool
fcitx5-fluent
];
};
};
```
### Fonts
```
fonts = {
packages = with pkgs; [
noto-fonts
noto-fonts-cjk-sans
noto-fonts-emoji
liberation_ttf
fira-code
fira-code-symbols
mplus-outline-fonts.githubRelease
dina-font
proggyfonts
wqy_microhei
wqy_zenhei
hack-font
nerd-font-patcher
jetbrains-mono
];
};
```
### Use Zsh and Beautify the Terminal
```
# Install zsh
programs.zsh = {
enable = true;
# enableCompletions = true;
autosuggestions.enable = true;
syntaxHighlighting.enable = true;
shellAliases = {
ll = "ls -l";
update = "sudo nixos-rebuild switch";
};
# history.size = 10000;
ohMyZsh = { # "ohMyZsh" without Home Manager
enable = true;
plugins = [ "git" "dirhistory" "history" "autojump" "catimg" "colorize" "sudo" ];
custom = "$HOME/.oh-my-zsh/custom/";
theme = "powerlevel10k/powerlevel10k";
};
};
```
And after `sudo nixos-rebuild switch`:
```
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"
```
If it prompts that omz cannot find a plugin, search for the plugin name on Bing, find their GitHub repository, and follow the steps in the README to install it.
### Install NVIDIA Drivers
Please refer to the wiki: [Nvidia - NixOS Wiki](https://nixos.wiki/wiki/Nvidia)
* * *
That's it for the blog. My installation was several weeks ago, so if there are any errors in the blog content, I welcome you to leave a comment below or contact me at: cloverta@petalmail.com
* * *
## References:
[Installation - NixOS Chinese](https://nixos-cn.org/tutorials/installation)
[Beginner Installation (NixOS from 0 Complete Collection Dream OS) - Xiao Lei's Random Notes](https://dev.leiyanhui.com/nixos/1base-install)
[Archlinux+Windows Dual Boot Installation Tutorial (UEFI) 2023.7 - NexusXian](https://www.cnblogs.com/NexusXian/p/17570030.html)
[KDE - NixOS Wiki](https://wiki.nixos.org/wiki/KDE)
[GNOME - NixOS Wiki](https://wiki.nixos.org/wiki/GNOME)
[Setup Zsh + Oh-my-zsh + PowerLevel10K - NixOS (without Home-manager) - NixOS Discourse](https://discourse.nixos.org/t/setup-zsh-oh-my-zsh-powerlevel10k-nixos-without-home-manager/58868)
[NixOS Manual](https://nixos.org/manual/nixos/stable/#sec-changing-config)

87
src/blog/en/post-8.md Normal file
View file

@ -0,0 +1,87 @@
---
title: "About Me, and This Blog"
pubDate: 2026-03-26
description: "An end, and a beginning. Commemorating the blog's reopening."
author: "Cloverta"
image:
url: "https://files.seeusercontent.com/2026/03/25/0rSi/rikka-manga.jpeg"
alt: "rikka"
tags: ["Reflections"]
---
<p style="font-size: 0.85rem;"><i><sub>Content translated by <a href="https://www.deepseek.com/">DeepSeek</a>.</sub></i></p>
I've wanted a static blog for a very long time.
But I never wrote one, why?
<del>Because I'm lazy</del> (runs away
I first came into contact with blogs in our club's lab. At that time, the club teacher was training us in front-end and back-end development. As a freshman, my mindset was still that of a perfectly overfitted small-town exam-taker, with problem-solving skills comparable to a fully grown kiwi fruit. Adhering to the principle of "ask if you don't understand", whenever even a speck of red appeared on my computer screen (even a warning), I would run over and ask a senior what it was for.
Until I annoyed the senior.
The senior, young but already with graying hair, wearing a beige checkered collar shirt and dark blue jeans, lying in an ergonomic chair typing on a low-profile keyboard, frowned and, without turning his head, tossed out a sentence: "If you don't understand, you can copy it and search online."
Copy it and search online.
Copy it and search online.
Copy it and search online.
The senior's half-rimmed glasses reflected a festive red glow. A programmer's blush speaks louder than any words.
Copy it and search online.
This sentence echoed in my empty mind. It's no exaggeration to say that for someone like me, who had been taught by parents and teachers since childhood to "always ask if you don't understand, searching for answers is forbidden", the impact of this sentence was no less than a high-speed Fuxing train crashing into a perfectly ripe banana in the middle of the tracks.
<del>From then on, I embarked on the path of no return, Ctrl+C and Ctrl+V-ing code</del>.
Like many people, the first blog I encountered was CSDN. At that time, it hadn't yet <del>scared away a hundred million tech people</del> and was practically the white moonlight in my heart. There was even a period when seeing CSDN in search results gave me an inexplicable sense of security.
My first blog post was also on CSDN. Among the few articles I posted later, one actually reached an astonishing 30,000 reads (I know CSDN inflates data, but, but 30k, man!).
![pasted-image-1774475485913.webp](https://files.seeusercontent.com/2026/03/25/Rfn5/pasted-image-1774475485913.webp)
<p align="center"><sup>The CSDN account I registered back then</sup></p>
Then, as everyone knows, CSDN became more and more... bad, cramming in plaster-like ads without restraint and an increasingly ugly greed. The last straw that broke the camel's back was when someone added my communication group one day and asked me why I had changed my articles to be paid. That's when I suddenly realized that CSDN had, at some point, secretly set my articles with higher read counts to be viewable in full only by VIPs.
<del>Setting it like that is one thing, but the key point is they didn't give me a single cent of the revenue.</del>
At that moment, I told myself, I've had enough of this absurd platform, I'm going to build my own blog.
My initial choice was WordPress (I think this is the same for most people) because it basically requires no coding knowledge, has comprehensive features (even a bit too comprehensive), and with some panels, it can even be deployed with one click. From deployment to posting, the whole process can be done with zero code.
But the other side of high encapsulation and high integration is extremely low efficiency. First, WordPress's extremely counter-intuitive dashboard UI design. The moment I opened the dashboard, my eyes felt like they were being assaulted by Coconut Palm's advertisements. Options with no clear hierarchy were densely spread out before my eyes. At the same time, WordPress's lamentable loading speed was no different from making my already overwhelmed brain idle and block while waiting for it to load.
![pasted-image-1774479167963.webp](https://files.seeusercontent.com/2026/03/25/lP7v/pasted-image-1774479167963.webp)
<p align="center"><sup>Maybe my brain is single-core, but I really couldn't find the theme settings at first</sup></p>
Every time I needed to change settings in the dashboard felt like playing Aimlab, having to click as fast as possible while also not clicking the wrong thing. Clicking wrong in Aimlab has no penalty, but clicking the wrong button in WordPress brings several seconds of hard-lock loading time.
It was the final exam period at the time. Students were busy cramming a whole semester's worth of coursework into the last week before exams. Adopting a "if it works, it works" mentality, I updated a few blog posts on WordPress.
But after that, there was no sign of blog updates for a long time. When the New Year approached, a group friend clicked on the URL and said, "Cloverta still owes a QQbot Markdown tutorial!" By the next Spring Festival, they said again, "Cloverta still owes a blog post!" By the Lantern Festival, they didn't say anything, and by Qingming Festival, there was still no sign of him.
The group friends have not seen him to this day—it seems Cloverta indeed no longer updates.
<img
src="https://s2.loli.net/2024/12/15/2sJYvf6N1MObRXd.jpg"
alt="1733496064080.jpg"
style="display: block; margin: 0 auto; zoom: 25%;"
/>
<p align="center"><sup>I don't know why I'm posting this image. It's cute, please send me money.</sup></p>
The moment that made me decide to write a completely new blog from scratch was when I saw <b>[閉源 lib](https://ex-tasty.com)</b>'s blog.
The first impression upon opening the blog: harmonious color combinations, clear and concise layout, fast response speed, high-quality content, and genuine passion really shocked me. At that moment, I thought:
<del>So blogs can be this beautiful too?</del>
So I plagiarized... ahem, I mean, I drew a lot of inspiration from <b>[極限風味](https://ex-tasty.com)</b>'s design philosophy. After two days of staying up late, spending over 30 hours, I rewrote this blog based on <b>[Astro](https://astro.build/)</b>.
It's fast, lightweight, beautiful, and convenient. Compared to the WordPress theme that hadn't been updated in 4 years, it has better mobile adaptation. Although, as the first version, there are still some minor flaws not yet fixed, I will try to maintain it slowly over time.
By the way, this blog is also open source at <b>[ClovertaTheTrilobita/SanYeCao-blog](https://github.com/ClovertaTheTrilobita/SanYeCao-blog/)</b>, and the documentation should be detailed enough. Feel free to give it a try (

View file

@ -9,8 +9,6 @@ image:
tags: ["树莓派", "Linux", "教程"] tags: ["树莓派", "Linux", "教程"]
--- ---
**This blog post also has an English version, click here: [(tutorial) How to Stream Via Sunshine on Your Raspberry Pi 5](https://blog.cloverta.top/archives/203)**
## 0.在开始之前 ## 0.在开始之前
我们需要理解的是树莓派5是arm64架构处理器需要看准安装的sunshine版本。 我们需要理解的是树莓派5是arm64架构处理器需要看准安装的sunshine版本。

View file

@ -91,8 +91,6 @@ reader () {
那么我们上代码: 那么我们上代码:
semaphore rw = 1; int count = 0; semaphore mutex = 1; semephore w = 1; // 用于实现写优先 <div></div> writer () { while (1) { P(w); // 写进程先给w锁上锁上锁后后来的读进程都需等待这个写进程解锁。 P(rw); // 给缓冲区上锁 写文件; V(rw); V(w); // 解锁,允许读进程加入读队列。 } } <div></div> reader () { while (1) { P(w); // 新的读进程加入时先检查w锁是否上锁,若已上锁则阻塞等待。若未上锁则先给它上锁 P(mutex); if (count == 0) P(rw); count++; V(mutex); V(w); // 读进程加入队列之后将w锁解锁保证加入队列操作的原子性。 读文件; P(mutex); count--; if (count == 0) V(rw); V(mutex); } }
```c++ ```c++
semaphore rw = 1; semaphore rw = 1;
int count = 0; int count = 0;