記一次linux操作系統實驗

前言

最近完成了一個需要修改和編譯linux內核源碼的操作系統實驗,個人感覺這個實驗還是比較有意思的。這次實驗總共耗時4天,從對linux實現零基礎,通過查閱資料和不斷嘗試,直到完成實驗目標,在這過程中確實也收獲頗豐,特此記錄

實驗內容

  1. 實現系統調用int hide(pid_t pid, int on),在進程pid有效的前提下,如果on置1,進程被隱藏,用戶無法通過ps或top觀察到進程狀態;如果on置0且此前為隱藏狀態,則恢復正常狀態(考慮權限問題,只有root用戶才能隱藏進程)
  2. 設計一個新的系統調用int hide_user_processes(uid_t uid, char *binname),參數uid為用戶ID號,當binname參數為NULL時,隱藏該用戶的所有進程;否則,隱藏二進制映像名為binname的用戶進程
  3. 在/proc目錄下創建一個文件/proc/hidden,該文件可讀可寫,對應一個全局變量hidden_flag,當hidden_flag為0時,所有進程都無法隱藏,即便此前進程被hide系統調用要求隱藏。只有當hidden_flag為1時,此前通過hide調用要求被屏蔽的進程才隱藏起來
  4. 在/proc目錄下創建一個文件/proc/hidden_process,該文件的內容包含所有被隱藏進程的pid,各pid之間用空格分開

實現思路

對于要求1,首先要修改PCB,對應到源碼里面就是task_struct,在其中添加一個屬性hide,用來表示該進程是否需要隱藏;然后修改復制進程的系統調用,用于給hide屬性設置默認值0;最后修改列舉所有進程的系統調用,在其中加入一個判斷,如果進程的hide是1則不展示這個進程
(注:也有方法說是可以通過把pid設置為0來達到隱藏的效果,但是實測下來,在5.15.60的kernel里面,這樣做不能隱藏,所以只能通過劫持系統調用來實現)

對于要求2,則可以遍歷所有進程,把符合條件的進程的hide設置為1即可

對于要求3,最開始以為可以通過用戶態的文件操作來實現,結果后來發現/proc是個虛擬文件系統,所以需要在初始化proc文件系統時,添加一個hide條目,然后設置這個條目的write函數,來達到創建該文件的目的

對于要求4,也是用和要求3一樣的思路,只是這里需要設置read函數,然后遍歷所有進程,把hide為1的pid全部返回

實驗環境

操作系統使用的ubuntu 22.04
linux kernel代碼版本是5.15.60
虛擬機使用的是VM Ware Workstation Pro 16
注意:虛擬機硬盤大小建議為60GB,編譯內核代碼非常吃硬盤,本人在實驗中前前后后擴容了幾次硬盤,最終發現60GB是個比較合適的大小,內核源碼編譯安裝之后還能剩15GB左右(下一次編譯安裝還需要一些硬盤空間做緩存,所以剩15GB是比較合適的)

實驗流程

編譯與安裝內核

參考https://www.cnblogs.com/robotech/p/16152269.html 即可,如果這部分出錯了,網上可以找到的資料很多,這里不再贅述了
不過這一步一定要有耐心,源碼編譯很慢,第一次全量編譯估計會耗時一個多小時,可以用這個閑暇時間玩玩原神

完成要求一

把編譯和安裝的流程跑通以后,就開始進行源代碼的修改了

修改PCB

linux的PCB結構體是task_struct,這個定義位于include/linux/sched.h
Tips:如果想在linux源碼里找東西,可以用https://elixir.bootlin.com/ 這個網站,左邊選擇版本,右邊輸入關鍵字,即可查詢到
在這里插入圖片描述
在linux使用vim打開這個文件,往下翻,看到這段注釋
在這里插入圖片描述
按照提示添加屬性即可

修改fork

這部分的源碼是在kernel/fork.c中的copy_process函數里面
閱讀源碼,在合適的地方插入初始化hide屬性的代碼即可,這里我選擇的位置是復制完進程信息之后,即下圖所示的位置
在這里插入圖片描述

添加系統調用

這部分我是看網上的各種文章,東拼西湊,進行多次實驗之后才跑通的,事后想想,我應該最先去看linux kernel的官方手冊
https://docs.kernel.org/process/adding-syscalls.html (附上官方手冊)
以下是我自己添加系統調用的過程,這里用添加一個輸出Hello World的簡單系統調用來舉例子
首先找到kernel/sys.c,在文件末尾使用SYSCALL_DEFINE宏來定義系統調用的函數體

SYSCALL_DEFINE0(hello)
{printk("hello world.114514\n");return 0;
}

解釋一下,SYSCALL_DEFINE0(hello)表示定義一個含有0個參數的系統調用,名字是hello,通過查看sys.c里面其它函數的定義代碼可以得知,如果想要添加一個只有一個參數的系統調用,那么應該使用SYSCALL_DEFINE1(hide,pid_t,pid),其中hide是系統調用的名字,pid_t是第一個參數類型,pid是第一個參數的名字,2個參數的同理
printk是輸出日志,這個日志可以在sudo dmesg里面看到,printk支持使用%d,%s等對輸出進行格式化,用法類似于printf

接下來修改系統調用表,在arch/x86/entry/syscalls/syscall_64.tbl中合適的地方添加剛才寫的系統調用,這里我是添加在了334號系統調用之后的
在這里插入圖片描述
仿照上面334寫即可,其中hello是自己隨便起的名字,而sys_hello是系統調用的函數名,這個函數名是上面SYSCALL_DEFINE0里寫的函數名前面加上前綴sys_得到的

添加完成后可以嘗試編譯運行一下新的內核
可以使用uname -a查看當前內核是不是最新編譯的(看時間即可)
在這里插入圖片描述
下面將使用一段代碼來測試一下新添加的335號系統調用

#include <linux/kernel.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>int main(int argc,char **argv)
{printf("System call return %ld\n",syscall(335));return 0;
}

運行程序
在這里插入圖片描述
然后使用sudo dmesg查看
在這里插入圖片描述

編寫hide系統調用

hide系統調用的實現有2個思路,一個是遍歷所有進程,找到pid相符的進程,然后設置hide,另一個思路是通過pid找到進程,然后直接設置,這里采取后者
通過查找資料,可以得知,根據pid查找進程是用這段代碼
pid_task(find_vpid(pid),PIDTYPE_PID);
最終完整的系統調用代碼如下

SYSCALL_DEFINE2(hide,pid_t,pid,int,on)
{struct task_struct * me = NULL;me=pid_task(find_vpid(pid),PIDTYPE_PID);if(current->uid != 0){//User is not rootreturn 0;}if(me == NULL){return 0;}if( on == 1 ){me->hide = 1;}else{if( me->hide == 1 ){me->hide = 0;}}return 0;
}

接下來再修改系統調用表即可完成系統調用的添加

劫持獲取所有進程的函數

現在已經可以通過系統調用來設置PCB里面的hide,下一步就是修改列舉所有進程的函數,讓它在列舉時判斷一下,如果hide==1就不列舉

proc文件系統

在劫持之前,需要簡單介紹一下proc文件系統。在linux根目錄下,有一個/proc文件夾,這其實并不是在磁盤上真實存在的文件,而是一個虛擬文件系統。
proc文件夾里面有很多個以pid為名字的文件夾,這些文件夾里面又有若干個文件,讀取這些文件就可以獲取這個進程的相關信息,例如想查看pid為1的程序的名字可以使用sudo cat /proc/1/comm
這一系列操作在系統底層的實現是:系統在啟動的時候就掛載了一個proc虛擬文件系統,當用戶訪問proc文件夾下的文件時,系統會調用proc文件系統里面相關的函數,而不是常規文件系統的函數,例如在執行ls /proc時,實際上系統會調用位于s/proc/base.c里面的proc_pid_readdir函數,這個函數會獲取當前系統中所有的進程,隨后會有函數把這個函數的返回值寫入到讀取文件操作的緩沖區中

修改代碼

所以,我們的突破口就是proc_pid_readdir函數,在閱讀這個函數的代碼之后,可以找到突破口是一個put_task_struct函數的調用,如下圖
在這里插入圖片描述
那么只需要在這個if里面加上一個條件,即必須這個進程不被隱藏才能put,即可完成劫持

結果驗證

在修改完源代碼之后,重新編譯和安裝內核,啟動新的內核
使用下面這段代碼來測試

#include <linux/kernel.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main(int argc,char **argv)
{int pid;int hide;scanf("%d %d",&pid,&hide);printf("System call return %ld\n",syscall(336,pid,hide));return 0;
}

編譯運行程序
在這里插入圖片描述
從圖中可以看出,進程順利隱藏,并且能夠重新展示,要求一順利實現

完成要求二

要求二是在要求一的基礎上進行一些簡單的擴展,這里可以使用一個比較暴力的思路,就是遍歷所有進程,然后挨個判斷uid和進程名稱,把符合要求的進程的hide設置為1即可
這里只有三點需要注意一下
1.遍歷所有進程可以使用for_each_process這個宏來完成,這個宏有類似于for循環的作用,用法如下

struct task_struct* p;
for_each_process(p){//Do something.....
}

這個宏的定義在include/linux/sched/signal.h里面,定義如下

#define for_each_process(p) \for (p = &init_task ; (p = next_task(p)) != &init_task ; )

2.用戶態的字符串不能在內核態直接使用,需要調用strncpy_from_user把用戶態的字符串復制到內核態的緩沖區才能使用,方法如下

char tmp_buf[256];
if(binname != NULL)strncpy_from_user(tmp_buf,binname,256);

最終的系統調用代碼如下

SYSCALL_DEFINE2(hide_user_processes,uid_t,uid,char*,binname)
{uid_t curr_uid=current->uid;if(curr_uid != 0){//User is not rootreturn 0;}char tmp_buf[256];if(binname != NULL)strncpy_from_user(tmp_buf,binname,256);struct task_struct* p=NULL;for_each_process(p){if(p->real_cred->uid.val == uid){if(binname == NULL){p->hide=1;}else{char* s=p->comm;int identical=1;int i=0;for(i=0;tmp_buf[i]!='\0' && s[i] != '\0';i++){if(tmp_buf[i] != s[i]){identical=0;break;}}if(tmp_buf[i] != s[i])identical=0;if(identical == 1){p->hide=1;}}}}return 0;
}

在編譯和安裝完成之后可以寫一段測試代碼來驗證一下代碼的正確性

#include <linux/kernel.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>int main(int argc,char **argv)
{int uid;char binname[20];scanf("%d %s",&uid,binname);printf("%s\n",binname);bool noBinname=false;if(strcmp(binname,"no") == 0){printf("Bin name set null\n");noBinname=true;}printf("System call return %ld\n",syscall(337,uid,noBinname?NULL:binname));return 0;
}

編譯運行該程序
在這里插入圖片描述
要求二完成

完成要求三

思路分析

之前提到過的,proc文件系統是一個虛擬文件系統,讀取和寫入proc文件夾下的文件的操作會交給一些特定的內核函數來執行,那么我們只需要添加一個/proc/hide條目,并配置這個條目的write函數,當write被調用的時候就根據寫入的值設置一個全局變量,然后再修改proc_pid_readdir函數,添加一個判斷,如果這個全局變量為0就不隱藏任何進程,這樣就可以達到設置全局開關的目的

全局變量的定義和使用

全局變量可以跨文件被使用,在需要使用全局變量的地方使用extern關鍵字聲明全局變量即可
需要注意的是,全局變量需要進行一次初始化,并且僅可以進行一次初始化
具體而言,可以這樣操作:在需要使用全局變量hidden_flag的c文件里面使用下面這條語句進行聲明

extern int hidden_flag;

然后在某個c文件中對hidden_flag變量進行定義

extern int hidden_flag;
int hidden_flag=1;

注意:聲明是告訴編譯器我這里有一個名叫hidden_flag的變量,我接下來會用這個變量,這個變量具體在哪需要編譯器自己去找;而定義則是告訴編譯器我新建了一個名為hidden_flag的變量,相當于真正為這個變量分配了內存空間

添加proc條目

大體流程

通過查閱資料和反復實驗,我找到了在5.15.60版本添加proc條目的方法
proc文件系統的初始化函數在fs/proc/root.c里面,名叫proc_root_init
網上很多教材是要修改一個名叫proc_misc_init函數,但是在這個版本的內核源碼里面找不到這個函數,所以索性就在proc_root_init函數里面添加條目了(因為看網上的代碼,root_init是會調用misc_init的,所以猜測直接在root_init里面添加應該也是可以的,最后實踐證明確實可行)

添加proc條目需要調用proc_create函數,該函數的定義如下:

struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct proc_ops *proc_ops);

可以看到,這個函數需要4個參數,第一個是文件名,這里要創建一個/proc/hidden,所以這個參數傳hidden;第二個參數是權限,為了防止后續因為權限問題導致實驗翻車,這里就給666了;第三個是parent,傳NULL即可;第四個是這個條目操作的配置項的指針,可以在這里配置該條目的read和write函數

下面開始添加proc條目
首先要實現該條目的read和write函數,當用戶態程序讀取和寫入/proc/hidden時,這兩個函數就會被調用

read函數

下面是read函數的定義

ssize_t hidden_read_proc(struct file *filp,char *buf,size_t count,loff_t *offp);

第一個參數是文件;第二個參數是用戶態的讀取緩沖區,我們需要往這個緩沖區里面寫數據來完成讀取操作;第三個參數是這個用戶態緩沖區的大小;第四個參數是上一次讀取的位置,因為可能出現緩沖區不夠等情況,用戶態程序在讀文件時通常是用下面的方式進行多次讀取的

char buf[256];
int len;
while((len=read(buf))!=0){//此時buf中讀取了len字節的數據,進行相應處理
}

所以read函數要做的事情就是往緩沖區中寫入數據,修改offp,然后返回已經讀入的字節數,下面是/proc/hidden條目的read函數的實現

ssize_t hidden_read_proc(struct file *filp,char *buf,size_t count,loff_t *offp )
{if(*offp > 0)return 0;char msg[256];int len=sprintf(msg,"Current flag is %d\n",hidden_flag);copy_to_user(buf,msg,len);*offp=len;return len;
}

需要注意的是,如果,沒有最開始判斷offp這一行,那么會出現讀取/proc/hidden文件讀不完的情況,具體而言,如果使用指令cat /proc/hidden,那么它會一直源源不斷地蹦出字符,不會停,這是因為read函數始終不會返回0,導致那個while循環不會停
此外,同樣的,內核態的內存和用戶態的內存是不互通的,需要使用copy_to_user函數來完成內存的拷貝

write函數

write函數的定義如下

ssize_t hidden_write_proc(struct file *filp,const char *buf,size_t count,loff_t *offp);

參數的意義和read是類似的,第二個參數是用戶即將寫入的數據緩沖區地址,第三個則是數據量,以下是write函數的具體實現

ssize_t hidden_write_proc(struct file *filp,const char *buf,size_t count,loff_t *offp)
{char msg[2056];copy_from_user(msg,buf,count);hidden_flag=msg[0]-'0';return count;
}

需要注意的是,同樣的,需要進行從用戶態到內核態的內存拷貝

proc_ops結構體

接下來新建一個proc_ops結構體的對象,傳入我們寫的read和write函數

struct proc_ops hidden_proc_fops = {proc_read:  hidden_read_proc,proc_write: hidden_write_proc
};

當然,這個結構體還支持我們配置更多的內容,具體可以看這個結構體的定義,這里不再贅述了

調用proc_create

最后調用proc_create,傳入參數,即可完成條目的創建

proc_create("hidden",666,NULL,&hidden_proc_fops);

結果驗證

我們重新編譯安裝內核,然后隱藏一個進程,然后再向/proc/hidden里面寫入0
在這里插入圖片描述
可見,hidden_flag起效果了,要求三完成

完成要求四

有了要求三的鋪墊,要求四就顯得比較簡單了,只需要實現一個read函數,在其中遍歷所有進程,把hide為1的進程pid返回即可
read函數的實現如下

ssize_t pid_read_proc(struct file *filp,char *buf,size_t count,loff_t *offp ) 
{if(*offp > 0)return 0;char msg[1024];int len=0;struct task_struct* p;for_each_process(p){if(p->hide == 1){len += sprintf(msg+len,"%d ",p->pid);}}copy_to_user(buf,msg,len);*offp=len;return len;
}

可以把1000用戶所有進程隱藏了,然后查看/proc/hidden_process文件來檢查效果
在這里插入圖片描述
可以在里面看到所有被隱藏的進程的pid,要求四完成

總結與心得

這次實驗的代碼量并不多,操作步驟也不復雜,主要的時間都花在了學習linux內核編程上面了。從零開始學習proc文件系統,linux源碼,并建立臨時知識體系,然后根據學到的東西進行開發實踐,這是一個充滿挑戰性但也非常有意思的過程。在這過程中,我學到了linux內核編程的技術,跑通了從內核源碼修改到最終運行的全流程,并對proc虛擬文件系統進行了更深入的自學,完成了四個實驗要求。
個人感覺這過程中查資料自學的效率有點低,下次遇到此類問題應該首先查找官方的手冊和教程,而不是在網上胡亂找相關的文章。
總的而言,收獲很多,這是一次非常有意思的經歷。
Anyway,寫這篇博客也是記錄一下這次實驗的經歷,感悟和收獲,同時也為其他做這個實驗的同學提供一點過來人的經驗,希望能起到避坑的效果。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/167530.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/167530.shtml
英文地址,請注明出處:http://en.pswp.cn/news/167530.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

pytorch模型優化簡介,未完結版

如有幫助&#xff0c;點贊收藏關注&#xff01; 如需轉載&#xff0c;請注明出處&#xff01; 今天來介紹torch模型中的優化器 優化是指在每個訓練步驟中調整模型參數以減少模型誤差的過程。 優化算法定義如何執行這個過程 所有優化邏輯都封裝在優化器對象中。在這里&#xf…

【黑馬甄選離線數倉day04_維度域開發】

1. 維度主題表數據導出 1.1 PostgreSQL介紹 PostgreSQL 是一個功能強大的開源對象關系數據庫系統&#xff0c;它使用和擴展了 SQL 語言&#xff0c;并結合了許多安全存儲和擴展最復雜數據工作負載的功能。 官方網址&#xff1a;PostgreSQL: The worlds most advanced open s…

音視頻項目——RTSP服務器解析(1)

介紹 利用線程池&#xff0c;實現 RTSP 服務器的高并發請求處理。 RTSP 是音視頻的控制視頻的協議&#xff0c;如果您還不了解&#xff0c;可以看看之前我解析 RTSP 協議的文章。音視頻協議解析(RTP/RTCP/RTSP/RTMP)——RTSP解析-CSDN博客 解析 我們先來看 RTP 的實現。RTP 是音…

Django框架之auth模塊

目錄 一、Auth模塊引入 二、創建超級用戶(管理員) 三、依賴于auth_user表完成登錄注冊功能 【1】基礎登陸 【2】保存用戶狀態 【3】登錄后跳轉 (1) 登錄后才能訪問頁面 -- 局部配置 (2) 登錄后才能訪問頁面 -- 全局配置 (3) 小結 三、修改密碼 四、注銷 五、注冊…

Springboot將多個圖片導出成zip壓縮包

Springboot將多個圖片導出成zip壓縮包 將多個圖片導出成zip壓縮包 /*** 判斷時間差是否超過6小時* param startTime 開始時間* param endTime 結束時間* return*/public static boolean isWithin6Hours(String startTime, String endTime) {// 定義日期時間格式DateTimeFormatt…

【數據結構】—搜索二叉樹(C++實現,超詳細!)

&#x1f3ac;慕斯主頁&#xff1a;修仙—別有洞天 ??今日夜電波&#xff1a;消えてしまいそうです—真夜中 1:15━━━━━━?&#x1f49f;──────── 4:18 &#x1f504; ?? ? ??…

函數計算的新征程:使用 Laf 構建 AI 知識庫

Laf 已成功上架 Sealos 模板市場&#xff0c;可通過 Laf 應用模板來一鍵部署&#xff01; 這意味著 Laf 在私有化部署上的擴展性得到了極大的提升。 Sealos 作為一個功能強大的云操作系統&#xff0c;能夠秒級創建多種高可用數據庫&#xff0c;如 MySQL、PostgreSQL、MongoDB …

js實現獲取原生form表單的數據序列化表單以及將數組轉化為一個對象obj,將數組中的內容作為對象的key轉化為對象,對應的值轉換為對象對應的值

1.需求場景 哈嘍 大家好啊&#xff0c;今天遇到一個場景&#xff0c; js實現獲取原生form表單的數據序列化表單以及將數組轉化為一個對象obj&#xff0c;將數組中的內容作為對象的key轉化為對象&#xff0c;對應的值轉換為對象對應的值 數組對象中某個屬性的值&#xff0c;轉…

元宇宙現已開放!

在 2023 年 11 月 3 日 The Sandbox 首個全球創作者日上&#xff0c;The Sandbox 聯合創始人 Arthur Madrid 和 Sebastien Borget 宣布元宇宙已開放&#xff0c;已創作完整體驗的 LAND 持有者可以自行將體驗發布至 The Sandbox 地圖上。 精選速覽 LAND 持有者&#xff1a;如果…

在JVM中 判定哪些對象是垃圾?

目錄 垃圾的條件 1、引用計數法 2、可達性分析 3、強引用 4、軟引用 5、弱引用 6、虛引用 判斷垃圾的條件 在Java虛擬機&#xff08;JVM&#xff09;中&#xff0c;垃圾收集器負責管理內存&#xff0c;其中的垃圾收集算法用于確定哪些對象是垃圾&#xff0c;可以被回收…

VBA即用型代碼手冊之工作薄的關閉保存及創建

我給VBA下的定義&#xff1a;VBA是個人小型自動化處理的有效工具。可以大大提高自己的勞動效率&#xff0c;而且可以提高數據的準確性。我這里專注VBA,將我多年的經驗匯集在VBA系列九套教程中。 作為我的學員要利用我的積木編程思想&#xff0c;積木編程最重要的是積木如何搭建…

[Latex] Riemann 問題中的激波,接觸間斷,膨脹波的 Tikz 繪圖

Latex 代碼 \begin{figure}\begin{subfigure}[b]{0.32\textwidth}\centering\resizebox{\linewidth}{!}{\begin{tikzpicture}\coordinate (o) at (0,0);\coordinate (Si) at (2.5,2.5);\coordinate (x) at (1,0);\draw[->] (0,0) -- (3,0) node[right] {$x$};\draw[->] …

ArkTS-自定義組件學習

文章目錄 創建自定義組件頁面和自定義組件生命周期自定義組件和頁面的區別頁面生命周期(即被Entry修飾的組件)組件生命周期(即被Component修飾的組件) Builder裝飾器&#xff1a;自定義構建函數按引用傳遞參數按值傳遞參數 BuilderParam裝飾器&#xff1a;引用Builder函數 這個…

Python 將列表拼接為一個字符串,Python join

目錄 join方法的源碼&#xff1a; 列表數據為字符串 列表數據為數字 三引號也可以使用join join方法的源碼&#xff1a; def join(self, abNone, pqNone, rsNone): # real signature unknown; restored from __doc__"""Concatenate any number of strings.T…

harmonyos應用開發者高級認證考試部分答案

1只要使用端云一體化的云端資源就需要支付費用&#xff08;錯&#xff09; 2所有使用Component修飾的自定義組件都支持onPageShow&#xff0c;onBackPress和onPageHide生命周期函數。&#xff08;錯&#xff09; 3 HarmonyOS應用可以兼容OpenHarmony生態&#xff08;對&#…

一文讀懂如何安全地存儲密碼

目錄 引言 明文存儲 基本哈希存儲 加鹽哈希存儲 適應性哈希算法 密碼加密存儲 小結 引言 密碼是最常用的身份驗證手段&#xff0c;既簡單又高效。密碼安全是網絡安全的基石&#xff0c;對保護個人和組織信息的安全具有根本性的作用。然而有關密碼泄漏的安全問題一再發生…

生物動力葡萄酒和有機葡萄酒一樣嗎?

農業維持了數十萬年的文明&#xff0c;但當人類以錯誤的方式過多干預&#xff0c;過于專注于制造和操縱產品時&#xff0c;農業往往會失敗。如果我們的目標是獲得最高質量的收成&#xff0c;并長期堅持我們的做法&#xff0c;我們就必須與土地打交道。 當我們開始尋找生物動力…

應用內測分發平臺如何上傳應用包體?

●您可免費將您的應用&#xff08;支持蘋果.ios安卓.apk文件&#xff09;上傳至咕嚕分發平臺&#xff0c;我們將免費為應用生成下載信息&#xff0c;但咕嚕分發將會對應用的下載次數進行收費&#xff08;每個賬號都享有免費贈送的下載點數以及參加活動的贈送點數&#xff09;&a…

UVA1025 城市里的間諜 A Spy in the Metro

UVA1025 城市里的間諜 A Spy in the Metro 題面翻譯 題目大意 某城市地鐵是一條直線&#xff0c;有 n n n&#xff08; 2 ≤ n ≤ 50 2\leq n\leq 50 2≤n≤50&#xff09;個車站&#xff0c;從左到右編號 1 … n 1\ldots n 1…n。有 M 1 M_1 M1? 輛列車從第 1 1 1 站開…

【電路筆記】-分壓器

分壓器 文章目錄 分壓器1、概述2、負載分壓器3、分壓器網絡4、無功分壓器4.1 電容分壓器4.2 感應分壓器 5、總結 有時&#xff0c;需要精確的電壓值作為參考&#xff0c;或者僅在需要較少功率的電路的特定階段之前需要。 分壓器是解決此問題的一個簡單方法&#xff0c;因為它們…