diff --git a/src/blog/en/post-1.md b/src/blog/en/post-1.md index 3fb19d5..3912f09 100644 --- a/src/blog/en/post-1.md +++ b/src/blog/en/post-1.md @@ -1,24 +1,440 @@ --- -title: '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试' -pubDate: 2022-07-01 -description: 'This is the first post of my new Astro blog.' -author: 'Astro Learner' +title: "(Step-by-Step Code Explanation) Building a QQ Group Chat Bot with Nonebot2 + Official API" +pubDate: 2025-04-14 +description: "Built a QQ bot with Nonebot2 and the official API, together with the group homies." +author: "Cloverta" image: - url: 'https://s2.loli.net/2022/05/01/UNzy8c6pTHBSuMO.jpg' - alt: 'The Astro logo on a dark background with a pink glow.' -tags: ["astro", "blogging", "learning in public", "Hello"] + url: "https://files.seeusercontent.com/2026/03/25/fiG2/pasted-image-1774456117575.webp" + alt: "img" +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. +

Content translated by DeepSeek.

-## 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. \ No newline at end of file +[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). \ No newline at end of file diff --git a/src/blog/en/post-2.md b/src/blog/en/post-2.md index 824d44a..d86fb31 100644 --- a/src/blog/en/post-2.md +++ b/src/blog/en/post-2.md @@ -1,24 +1,153 @@ --- -title: 'My Second Blog Post' -pubDate: 2022-07-01 -description: 'This is the first post of my new Astro blog.' -author: 'Astro Learner' +title: "(Tutorial) Installing Sunshine on Raspberry Pi 5 for LAN Streaming" +pubDate: 2025-04-21 +description: 'Installing Sunshine on a Raspberry Pi' +author: "Cloverta" image: - url: 'https://files.seeusercontent.com/2026/03/25/0rSi/rikka-manga.jpeg' - alt: 'The Astro logo on a dark background with a pink glow.' -tags: ["astro", "blogging", "learning in public"] + url: "https://files.seeusercontent.com/2026/03/25/t2zJ/pasted-image-1774456500701.webp" + alt: "img" +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. +

Content translated by DeepSeek.

-## 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. \ No newline at end of file +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) \ No newline at end of file diff --git a/src/blog/en/post-3.md b/src/blog/en/post-3.md index 81eafa8..52785d7 100644 --- a/src/blog/en/post-3.md +++ b/src/blog/en/post-3.md @@ -9,6 +9,8 @@ image: tags: ["Peterson's algorithm", "Operating System"] --- +

Content translated by ChatGPT>

+ 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: diff --git a/src/blog/en/post-4.md b/src/blog/en/post-4.md new file mode 100644 index 0000000..08ebf09 --- /dev/null +++ b/src/blog/en/post-4.md @@ -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"] +--- + +

Content translated by DeepSeek.

+ +## 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. \ No newline at end of file diff --git a/src/blog/en/post-5.md b/src/blog/en/post-5.md new file mode 100644 index 0000000..56f3497 --- /dev/null +++ b/src/blog/en/post-5.md @@ -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"] +--- + +

Content translated by DeepSeek.

+ +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 P0~P4 and 3 types of resources R0~R2. 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**. \ No newline at end of file diff --git a/src/blog/en/post-6.md b/src/blog/en/post-6.md new file mode 100644 index 0000000..2116dfd --- /dev/null +++ b/src/blog/en/post-6.md @@ -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"] +--- + +

Content translated by DeepSeek.

+ +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** \ No newline at end of file diff --git a/src/blog/en/post-7.md b/src/blog/en/post-7.md new file mode 100644 index 0000000..715ff7b --- /dev/null +++ b/src/blog/en/post-7.md @@ -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"] +--- + +

Content translated by DeepSeek.

+ +# 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) \ No newline at end of file diff --git a/src/blog/en/post-8.md b/src/blog/en/post-8.md new file mode 100644 index 0000000..f33247a --- /dev/null +++ b/src/blog/en/post-8.md @@ -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"] +--- +

Content translated by DeepSeek.

+ +I've wanted a static blog for a very long time. + +But I never wrote one, why? + +Because I'm lazy (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. + +From then on, I embarked on the path of no return, Ctrl+C and Ctrl+V-ing code. + +Like many people, the first blog I encountered was CSDN. At that time, it hadn't yet scared away a hundred million tech people 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) + +

The CSDN account I registered back then

+ +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. + +Setting it like that is one thing, but the key point is they didn't give me a single cent of the revenue. + +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) + +

Maybe my brain is single-core, but I really couldn't find the theme settings at first

+ +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. + +1733496064080.jpg + +

I don't know why I'm posting this image. It's cute, please send me money.

+ +The moment that made me decide to write a completely new blog from scratch was when I saw [閉源 lib](https://ex-tasty.com)'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: + +So blogs can be this beautiful too? + +So I plagiarized... ahem, I mean, I drew a lot of inspiration from [極限風味](https://ex-tasty.com)'s design philosophy. After two days of staying up late, spending over 30 hours, I rewrote this blog based on [Astro](https://astro.build/). + +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 [ClovertaTheTrilobita/SanYeCao-blog](https://github.com/ClovertaTheTrilobita/SanYeCao-blog/), and the documentation should be detailed enough. Feel free to give it a try ( \ No newline at end of file diff --git a/src/blog/zh/post-2.md b/src/blog/zh/post-2.md index 6cfd252..5a07f3c 100644 --- a/src/blog/zh/post-2.md +++ b/src/blog/zh/post-2.md @@ -9,8 +9,6 @@ image: 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.在开始之前 我们需要理解的是,树莓派5是arm64架构处理器,需要看准安装的sunshine版本。 diff --git a/src/blog/post-4.md b/src/blog/zh/post-4.md similarity index 87% rename from src/blog/post-4.md rename to src/blog/zh/post-4.md index d9c9d77..33adc07 100644 --- a/src/blog/post-4.md +++ b/src/blog/zh/post-4.md @@ -91,8 +91,6 @@ reader () { 那么我们上代码: -semaphore rw = 1; int count = 0; semaphore mutex = 1; semephore w = 1; // 用于实现写优先
writer () { while (1) { P(w); // 写进程先给w锁上锁,上锁后后来的读进程都需等待这个写进程解锁。 P(rw); // 给缓冲区上锁 写文件; V(rw); V(w); // 解锁,允许读进程加入读队列。 } }
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++ semaphore rw = 1; int count = 0; diff --git a/src/blog/post-5.md b/src/blog/zh/post-5.md similarity index 100% rename from src/blog/post-5.md rename to src/blog/zh/post-5.md diff --git a/src/blog/post-6.md b/src/blog/zh/post-6.md similarity index 100% rename from src/blog/post-6.md rename to src/blog/zh/post-6.md diff --git a/src/blog/post-7.md b/src/blog/zh/post-7.md similarity index 100% rename from src/blog/post-7.md rename to src/blog/zh/post-7.md diff --git a/src/blog/post-8.md b/src/blog/zh/post-8.md similarity index 100% rename from src/blog/post-8.md rename to src/blog/zh/post-8.md