2020 6.s081——Lab1:Xv6 and Unix utilities夢開始的地方

一任宮長驍瘦

臺高冰淚難流

錦書送罷驀回首

無余歲可偷

——知否知否

完整代碼見:6.s081/kernel at util · SnowLegend-star/6.s081 (github.com)

Lecture 01知識點總結

首先透徹理解Lecture01的知識很是重要

pid=wait((int *) 0);

“wait(int *status)”函數用于等待子進程的終止,它的參數是一個指向整數的指針(通常是 int * 類型),并且通常用來存儲子進程的終止狀態。這里“0”意思是將指針賦值為NULL,表示不關心子進程的退出狀態信息,這意味著不打算獲取子進程的終止狀態。

如果傳遞一個非NULL的指針給wait(int *status)函數,它將用來存儲子進程的退出狀態信息,以便可以檢查子進程的退出狀態,例如子進程是正常終止還是出錯等。在這種情況下,應該確保指針指向一個有效的內存位置,以便存儲子進程的狀態信息。而父進程可以利用status這個指針來獲取子進程的退出狀態。

exec(char *file, char *argv[])詳解

當調用exec(char *file, char *argv[])函數時,xv6 將會卸載當前進程的代碼和數據,并加載并執行指定路徑的新程序。新程序會接管當前進程的上下文,并開始執行。這意味著 exec 調用后,當前進程的地址空間、堆棧、文件描述符等狀態都會被新程序取代。

這對于實現進程的動態加載和替換,以及執行不同的程序非常重要。例如,當您在命令行中運行一個可執行程序時,實際上是通過 exec 系統調用來執行它,從而替換了當前的 shell 進程。

文件描述符

文件描述符fd其實就是代表了open操作對應的那個文件。例如

fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);

int write(int fd, char *buf, int n)

這里的fd就是指“example.txt”這文件;此外,write函數里面的fd不能替換為函數名。因為 write 函數需要一個文件描述符作為其第一個參數,而不是文件名稱。文件描述符是一個整數,用于標識已打開文件或其他 I/O 資源。

每個進程都維護一個獨立的文件描述符表,其中包含了該進程打開的所有文件和I/O資源的引用。

?

?

運行可執行文件的問題

對于一般的可執行文件a,要運行它的命名形式為“./a”,而不是直接在命令行輸入“a”來運行。只有當一個可執行文件位于系統的 PATH 路徑中時,可以直接輸入其文件名來運行它,而不需要指定完整的文件路徑。系統會在 PATH 中的各個目錄中查找這個可執行文件,如果找到了匹配的文件,就會執行它。

管道

return 0與exit(0)

int main()函數其實不用return 0,如果main函數沒有顯式的return語句,編譯器將會隱式地在函數末尾插入一個return 0語句,表示程序正常退出并返回0。但是,在xv6只用return 0則會出問題。

在 xv6 中,要正常終止一個進程,應該使用 exit(0) 系統調用而不是在 main 函數中使用 return 0。這是因為 xv6 通過系統調用來通知內核進程的結束,同時執行一些清理工作,以確保資源的正確釋放。exit(0) 系統調用會觸發這個行為,而簡單的 return 0 并不會。

在你提到的錯誤信息中,"usertrap(): unexpected scause 0x000000000000000d" 是一個異常信息,表明出現了意外的異常類型。當你在 main 函數中使用 return 0 時,進程沒有經過適當的清理,導致 xv6 報告了這個異常。

總結起來,為了正常終止 xv6 進程并避免異常錯誤,應該使用 exit(0) 而不是 return 0。這確保了進程的正確退出并執行必要的清理工作。

Boot xv6 (easy)

我奶奶都能過的lab。注意一點就是得在“xv6-labs-2020”這個文件夾底下運行“make qemu”,而不是在“xv6-labs-2020/user”這個文件夾底下運行它。

sleep

真正意義上夢開始的地方

自己編寫的文件得放在“user”文件夾里面,然后在 “Makefile”的“UPROGS”里面添加這個文件名,最后打開qemu進行運行。當然,我們發現不進入qemu也能運行“sleep”這條命令,但是這里的“sleep”命令和我們自己編寫的“sleep”函數有出入。那是因為“sleep”是系統的內置函數,只有打開qemu才能運行我們自己編寫的“sleep”函數。

今天在linux環境下想直接測試講義上的“fork”函數,結果總是報錯。問了GPT才發現原來書上給的一些函數調用是只有xv6才具有的,在普通的linux系統上并不具備這些函數調用。

sleep()實現如下


#include "kernel/types.h"
#include "user/user.h"int main(int argc, char const *argv[]){if(argc!=2){printf("Error!The function should obtain two argument.\n");exit(1);}sleep(atoi(argv[1]));exit(0);
}

pingpong

這個實驗真正意義上把我搞麻了,初次pipe簡直就是惡心至極。開始我一直有一個困擾,就是在父進程進行了pipe(p)之后進行fork(),此時父進程和子進程就都可以訪問這個管道p了。但是我看到好多題解里面明明在一個進程里面關閉了管道的一端比如“close(p[0])”,但是又在另一個進程里面用到了“read(po[0],buf,sizeof(buf))”。我的想法是既然子進程和父進程都可以對管道進行有效操作,那上述的兩個操作不就相悖了嗎?這個疑惑困擾了我兩天,那個時候四處尋找合適的解釋結果都不能很好地解答我的疑惑。然后我又在講義里看到了另一句話“在一個程序中創建子進程后,子進程將會繼承與父進程相同的文件描述符表”,這句話又讓我丈二和尚摸不著頭腦。后來去群里一問才得知這句話的本質和建立軟連接差不多。

?????? 就拿在父進程里面創建的管道來說,子進程自己復制了一份管道的引用。然后close()操作只是作用于這份引用,也就是說子進程自己不能再使用某個端口的引用了,但是這并不影響父進程對兩個端口的操作。領悟到這一層后,我又去測試了一番,果真是這樣。這個困擾我幾天的問題一解決,那pingpong的實現就變得有頭緒了。

?????? 對于實現過程,我最開始想不知道怎么保證父子進程之間的同步關系,在兩個進程里面都是先write()然后接著read()。由于進程執行的異步性必然會導致兩個進程的輸出交雜在一起,就像這樣“43:r:erceecieviev?epo?pnigng”。

后來想到的可能的解決辦法是用wait()來保證子進程和父進程之間的同步關系。但是這個技巧在這題行不通,因為實驗要求我們先打印子進程收到了數據,這就要求父進程自己得先寫入,那把wait()放在父進程部分的開頭就會導致類似死鎖一樣的結果。參考率其他人的結果,他們都是通過控制子進程的write()部分在read()部分后面來保證輸出同步。用wait()一樣可以實現,把wait()放在父進程的wirte()后但是read()之前即可解決。

pingpong()實現如下
?


#include"kernel/types.h"
#include"user/user.h"
#include "kernel/stat.h"int main(){int pipe_ptc[2];    //父進程用來給子進程寫入信息的管道int pipe_ctp[2];    //子進程用來給父進程寫入東西的管道char* ptc_msg="ping",*ctp_msg="pong",ptc[256],ctp[256];if(pipe(pipe_ctp)==-1){printf("There is something wrong with pipe()!");exit(1);}if(pipe(pipe_ptc)==-1){printf("There is something wrong with pipe()!");exit(1);}if(fork()!=0){//子進程的fork()返回0write(pipe_ptc[1],ptc_msg,sizeof(ptc_msg));close(pipe_ptc[1]);                                 //關閉父進程管道的寫入端int parent_pid=getpid();wait((int *)0);if(read(pipe_ctp[0],ctp,256)!=-1){printf("%d: received pong\n",parent_pid);}close(pipe_ctp[0]);                                 //關閉子進程管道的讀入端exit(0);}else{int child_pid=getpid();write(pipe_ctp[1],ctp_msg,sizeof(ctp_msg));close(pipe_ctp[1]);                                 //關閉子進程管道的寫入端if(read(pipe_ptc[0],ptc,256)!=-1){printf("%d: received ping\n",child_pid);}close(pipe_ptc[0]);                                 //關閉父進程的讀入端exit(0);                                            //很重要,要不然子進程不會退出}exit(0);
}

primes

這題主要就是理解文檔給的那個“線程篩”圖——即讓父進程按從小到大的順序把所有的數都傳給子進程,然后子進程排除掉不能被第一個素數2整除的傳遞給孫進程,孫進程再排除掉不能被第二個素數3整除的傳遞給它的子進程,以此類推……很顯然,每個進程傳給它的子進程的那批數字里面第一個是最小的且是素數,遞歸得到的第i個進程可以得到第i-1個素數。

接下來詳細描述下處理過程:

假設最初始的進程p0 對那些數不做任何處理,直接從小到大一股腦傳給第一代子進程p1 。接下的一代代子孫進程對數據的處理就具有一般性了。首先,子進程pi-1 接收從它的父進程pi

傳過來的數據,且pi 傳過來的第一個數n1 為我們得到的第i-1個素數。pi-1 接收完數據后把那些不能被n1 整除的數據傳遞給它的子進程pi-2 ,而后進程pi-2

進行類似的處理。顯然,我們如果要完成上述進程的兩個通信過程,就勢必得用到兩個管道。同時,由于xv6的文件描述符有限,我們就要及時關閉用不到的管道端(即文件描述符)。這里提一句,務必養成及時關閉文件描述符的好習慣,不然最后總是會有各種輸出卡住的問題。

還有些實現的細節需要注意。我遇到的問題是一開始習慣性地創建了個讀緩沖區的數組buf[40],然后讀取管道中的數據就是read(0,buf,sizeof(buf))。乍一看沒問題,但是考慮到如果管道里面的數據沒有40個,那這個進程就會被阻塞在這里,等著管道的寫入端繼續寫入數據直到把40個數據讀滿。所以這題應當是一個整形一個整形地讀入數據。

?

?primes()實現如下
?

#include"kernel/types.h"
#include"user/user.h"
#include "kernel/stat.h"void child(int pipe_p2c[]){// int buf[40];                           //buf[0]里面的元素應該是最小的int elem,min;                          int pipe_s2g[2];                       //子進程給孫進程通信的管道close(pipe_p2c[1]);// for(i=0;i<40;i++)//     buf[i]=0;// if(read(pipe_p2c[0],buf,40)==0){   //如果沒東西可以讀了就可以開始退出了       不能一下子讀入sizeof(buf),因為管道里面的元素沒有這么多if(read(pipe_p2c[0],&min,sizeof(int))==0){close(pipe_p2c[0]);exit(0);}printf("prime %d\n",min);pipe(pipe_s2g);if(fork()!=0){//子進程準備給孫子進程寫東西了close(pipe_s2g[0]);                        //把管道的讀入端關掉再說// for(i=1;buf[i]!=0;i++){//     if(buf[i]%buf[0]!=0)                //不能相除的才傳給下一輩//         write(pipe_s2g[1],&buf[i],1);   //一個字節一個字節地寫入管道// }while(read(pipe_p2c[0],&elem,sizeof(int))!=0){if(elem%min!=0)write(pipe_s2g[1],&elem,sizeof(int));}close(pipe_p2c[0]);close(pipe_s2g[1]);wait(0);exit(0);}else{//孫子進程child(pipe_s2g);}
}int main(){int i;int pipe_p2c[2];pipe(pipe_p2c);if(fork()!=0){close(pipe_p2c[0]);for(i=2;i<=35;i++){//把這些數字依次寫入管道里面準備讓子進程讀write(pipe_p2c[1],&i,sizeof(int));}close(pipe_p2c[1]);//及時關閉文件描述符,不然輸出會在這里卡住wait(0);exit(0);        }else{child(pipe_p2c);}exit(0);
}

find

這題主要是對題目的要求思考了很久,一開始沒理解find命令在類unix機器上是怎么用的。查閱了下資料,發現這里的find命令大概格式如下“find path filename”,這里的path既可以是一個目錄如“./a/b”,也可以是一個具體的文件路徑“./a/c.txt”。

然后第二個困擾了我很久的點是題目的hint3:Don't recurse into "." and ".."我心想平時在windows系統或者是linux系統里面查看文件夾的內容時從來就沒有看到過“.”和“..”這兩個特殊的文件啊?后來問GPT說是用ls打開文件目錄就可以看到這兩個特殊的目錄項,嘗試了一番依然沒有發現。直到后面進入qemu的時候再調用ls發現上來就把這兩個特殊的目錄項給列出來了。對于命令中要含有“.”和“..”我也不理解是個怎樣的形式,問了GPT半天才發現形式可以如下“ls -l /path/to/some/directory/./../another/directory”。解決了這兩個疑惑,就可以著手完成find了。

根據hint1先看一遍lc.c很容易搞得自己一頭霧水,我看了兩遍之后還有些不得要領,遂直接開始照著lc.c來實現find的功能,邊寫邊理解。其實兩個最后先出來大同小異,就是find傳過去的參數不僅有“path”,還有“filename”。在lc.c原有的基礎上適當加上對“filename”的匹配處理即可。

從ls.c中我們可以看到先是用open()打開傳過去的“path”,看用戶給出的“path”是否能夠被訪問。然后用fstat()把這個文件的信息存入stat結構體中。處理完這個“path”后,如果path是個目錄,就開始對該目錄底下的目錄項進行遍歷,目錄項有文件和目錄兩種類型。對于目錄項是目錄的情況,我們又可以把這個子目錄信息存入dirent這個結構體中進行遞歸遍歷。

在完成實驗要求的find功能之后,我心血來潮統計了下qemu當前存在的目錄項。一個有趣的結果是“.”和“..”并沒有被計入在目錄項里面。這我就有一個猜測了,“.”和“..”本質就是軟連接。又發現了個很奇怪的問題,把“dir_ItemNum”放在if(de.inum)前面就會導致輸出結果總是為64,而且就算在xv6內部繼續添加文件也還是如此,這是為什么呢?但是如果再在當前目錄創建./a/b這個子目錄,統計結果又正常了。這時在使用命令“find . a”會發現dir_ItemNum的值增加了3。難不成xv6的當前目錄內置了文件上限是64且都已經被創建好了,只不過沒有內容導致這些目錄項的inum是0,但是遍歷當前文件夾的時候還是會遍歷之?

find()如下
?

#include"kernel/types.h"
#include"kernel/stat.h"
#include"user/user.h"
#include"kernel/fs.h"char *fmtname(char *path){static char buf[DIRSIZ+1];char *p;//find first character after last slashfor(p=path+strlen(path);p>path&&*p!='/';p--);p++;//return blank-padded nameif(strlen(p)>=DIRSIZ)return p;memmove(buf,p,strlen(p));memset(buf+strlen(p),' ',DIRSIZ-strlen(p));return buf;
}int flag=0;
int dir_ItemNum=0;    //看一下當前目錄底下有多少條信息 “.”和“..”沒被統計進去void find(char *directory,char *filename){char buf[512],*p;       //p作為定位指針int fd;struct dirent de;struct stat st;if((fd=open(directory,0))<0){printf("find: cannot open %s\n",directory);exit(1);}if(fstat(fd,&st)<0){printf("find: cannot stat %s\n",directory);close(fd);exit(1);}struct stat stat_temp;  //這句話不能定義在case內部嗎?    switch(st.type){case T_DEVICE:case T_FILE:if(strcmp(fmtname(directory),filename)==0){printf("%s\n",directory);flag=1;}exit(0);case T_DIR:if(strlen(directory)+1+DIRSIZ+1>sizeof(buf)){printf("find: path too long\n");break;}strcpy(buf,directory);              //buf用來存當前正在訪問目錄的路徑p=buf+strlen(buf);*p++='/';                           //把p挪到buf的最后一個元素上while(read(fd,&de,sizeof(de))==sizeof(de)){// dir_ItemNum++;  //怎么把這句放在if(d.inum)前面只能輸出64呢?                if(de.inum==0)                  //如果 inum 字段等于 0,通常表示該目錄項無效或未使用continue ;memmove(p,de.name,DIRSIZ);      //把文件名都存在buf剛添上的“/”后面p[DIRSIZ]=0;if(stat(buf,&stat_temp)<0){     //這不是多此一舉嗎  有用的,可以判斷當前目錄下打開的文件是什么類型printf("find: cannot stat %s\n",buf);continue;}if(stat_temp.type==T_FILE)      //如果是文件類型if(strcmp(de.name,filename)==0){printf("%s\n",buf);flag=1;                        }if(stat_temp.type==T_DIR){if((strcmp(de.name,".")==0)||(strcmp(de.name,"..")==0))     //得排除掉“.”和“..”這兩個目錄防止無限遞歸continue ;find(buf,filename);         //遞歸訪問這個目錄 開始把buf寫成de.name了,有點小丑                   }}}return ;
}int main(int argc,char *argv[]){if(argc!=3){printf("Usage: find <directory> <filename>\n");exit(1);}find(argv[1],argv[2]);printf("%d\n",dir_ItemNum);if(flag==0)printf("Fail to find the file '%s'!\n",argv[2]);exit(0);
}

xargs

這個這個函數的實驗要求十分怪異,我看半天沒理解到底要實現哪些功能,還以為得實現到“find xx xx | xargs echo xxx”這種程度。誰知它只需要實現和echo的組合功能即可,這就簡單不少了。

下面來簡單介紹下管道命令“|”和“xargs”在類unix系統里的用法。用

$ echo “hello” | grep root

管道命令的作用,是將左側命令(cat “hello)的標準輸出轉換為標準輸入,提供給右側命令(grep root)作為參數。但是,大多數命令都不接受標準輸入作為參數,只能直接在命令行輸入參數,這導致無法用管道命令傳遞參數。舉例來說,echo命令就不接受管道傳參。而xargs命令的作用,是將標準輸入轉為命令行參數。所以一般情況下“|”和“xargs”是穿一條褲子的。

實驗給出的hint是用fork和exec兩個調用來實現這個功能。我們得注意一點,我們正在實現的函數是“xarg.c”!所以agrcargc[]兩個參數是以命令行中的xagrs命令以及它后面跟著的參數為操作對象的,千萬不要以為這兩個參數讀取的是整個命令行的全部命令和參數!我開始就因為弄混了又搞半天才發現問題所在。如果想要讀取整個命令行的數據,我們可以利用“read(0,p,1)”來一個字符一個字符地讀取所有內容,直到讀取到“\0”為止。因為在許多操作系統中,標準輸入(stdin)的文件描述符通常默認已經打開,因此可以直接調用 read(0, buf, sizeof(buf)) 來從標準輸入讀取數據。

這里又有個小坑,開始問GPT“假如char p=' ',那while(p)會直接跳出嗎?”GPT回答說是,我開始信以為真,直到去dev c++上去測試了下并不然。只有p=‘\0’時while(p)才能直接退出。

踩完以上兩個坑后,思路就明了了起來。當前進程是實現xargs,那就得再fork()一個子進程,子進程調用exec()來實現整個命令行的命令。需要注意的是題目中要求為每一行執行一個命令,所以用數組char** arguments來存儲命令行的命令的時候是一行一行地讀取,即一行讀一個命令或者參數。這里用不用管道都可以實現父進程給子進程傳遞xargs跟著的相應參數。

xargs代碼如下

#include"kernel/types.h"
#include"kernel/stat.h"
#include"user/user.h"
#include"kernel/fs.h"
#include"kernel/param.h"int main(int argc,char *argv[]){int i;char buf[128]={0};int len;char* arguments[32];//用來讀入命令行的輸入if(argc==1){printf("Usage: xagrs [Command] [para1] ...[para n]");exit(1);}for(i=1;i<argc;i++){len=sizeof(argv[i])+1;arguments[i-1]=(char *)malloc(sizeof(char)*len);strcpy(arguments[i-1],argv[i]);    //不能簡單地用“=”來賦值,兩者的類型是不一樣的}i--;    //因為這里i已經到了agrc那么大,但實際上arguments數組的下標才記到i-1char *p=buf;read(0,p,1);while(*p){          //苦也,GPT誤我!if(*p=='\n'){ //如果讀到一行末尾*p='\0';    //加上'\0'從而構成字符串len=sizeof(buf)+1;arguments[i]=(char*)malloc(sizeof(char)*len);strcpy(arguments[i],buf);i++;memset(buf,0,128);  //將buf置為初始狀態p=buf;  //將p指針重新定位到buf開頭read(0,p,1);continue;}p++;read(0,p,1);}arguments[i]=0;if(fork()==0){if(exec(argv[1],arguments)==-1){printf("xargs: exec failed.\n");exit(1);}}wait(0);    //回收子進程exit(0);
}

If you fail a test, make sure you understand why your code fails the test. Insert print statements until you understand what is going on.

今天剛在群里看到說相比于gdb,直接用printf檢查代碼的執行情況會更為簡單方便。經歷了CSAPP的拷打之后,我對此深以為然。還記得proxy lab就是printf大法立功。今天第一次打開s081的Lab guidance,發現了上面那句話,看來printf的好用之處已經廣為人知了。無知時詆毀printf,成長時理解printf,成熟時加入printf

最后為了先把項目上傳到github的倉庫里面,也是折騰了一晚上,索性終有所得。

?

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

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

相關文章

Python變量用法——單下劃線變量名_

單下劃線變量名_ 在常用的諸多變量中&#xff0c;單下劃線_是比較特殊的一個。它常作為一個無意義的占位符出現在賦值語句中。_這個名字本身沒有什么特別之處&#xff0c;這算是大家約定俗成的一種用法。 舉例&#xff0c;假如你想在解包賦值時忽略某些變量&#xff0c;就可以…

Day54 判斷子序列 + 不同的子序列

392 判斷子序列 題目鏈接&#xff1a;392. 判斷子序列 - 力扣&#xff08;LeetCode&#xff09; 給定字符串 s 和 t &#xff0c;判斷 s 是否為 t 的子序列。 字符串的一個子序列是原始字符串刪除一些&#xff08;也可以不刪除&#xff09;字符而不改變剩余字符相對位置形成…

記一次服務器數據庫被攻擊勒索

如圖&#xff0c;早上一起來就發現&#xff0c;我的MongoDB數據庫里面的信息全部沒有了&#xff0c;只留下一段話。 大致意思就是&#xff1a;我的數據庫的數據被他們備份然后全部刪掉了&#xff0c;我必須要支付0.0059的bitcoin&#xff08;折合400美刀&#xff09;來贖回我的…

Springboot+WebSocket實現消息推送

WebSocket是一種在單個TCP連接上進行全雙工通信的協議。WebSocket通信協議于2011年被IETF定為標準RFC 6455&#xff0c;并由RFC7936補充規范。WebSocketAPI也被W3C定為標準。 WebSocket使得客戶端和服務器之間的數據交換變得更加簡單&#xff0c;允許服務端主動向客戶端推送數…

學習率調整

學習率調整 import mathdef adjust_learning_rate(optimizer, epoch, args):"""Decay the learning rate with half-cycle cosine after warmup"""if epoch < args.warmup_epochs:lr args.lr * epoch / args.warmup_epochs else:lr args.m…

不是,你不會還在用雙層遍歷循環來做新舊數組對比,尋找新增元素吧?

目錄 一、雙層循環遍歷 1.1、雙循環錯誤示范 1.2、正確的做法 ①使用array.includes() ②使用set 二、array.includes()的使用與技巧 2.1、基本語法 2.2、返回值 2.3、使用技巧 2.3.1、用戶輸入驗證 2.3.2、權限檢查 2.4、兼容問題 三、總結 一、雙層循環遍歷 1.…

【重學C語言】十七、預處理指令

【重學C語言】十七、預處理指令 預處理指令預定義宏`#define` 宏定義示例注意事項特殊符號條件編譯頭文件包含`#pragma`預處理指令 C語言中的預處理指令(Preprocessor Directives)是一種特殊的指令,它們在編譯過程的早期階段(即實際編譯之前)被預處理器(Preprocessor)處…

OpenCV學習 基礎圖像操作(十六):圖像距離變換

基礎原理 顧名思義&#xff0c;我們可以利用像素之間的距離作為對該像素的一種刻畫&#xff0c;并將其運用到相應的計算之中。然而&#xff0c;在一幅圖像之中&#xff0c;某種類型的像素并不是唯一的&#xff0c;因此我門常計算的是一類像素到另一類的最小距離&#xff0c;并…

My Spirit | “頂級復盤”

世界不會在意你的自尊&#xff0c; 人們看到的只是你的成就。 在你沒有成就之前&#xff0c; 切勿過分強調自尊。 ——菲茨杰拉德《了不起的蓋茨比》 目錄 My Spirit | “頂級復盤”00 | 日復盤01 | 周復盤2.1 周計劃2.2 周復盤2.3 下步計劃2.4 下步總結 02 | 月復盤2.1 本月目…

香橙派KunPengPro評測

一、引言 二、開箱 2.1、主要包含說明 1、充電器(贈typec-c線) 2、香橙派kunpengpro(已經帶裝好帶散熱器) 3、SD卡(32G)(已經帶裝好系統openEuler 22.03 (LTS-SP3)) (注意&#xff1a;上電接HDMI線可直接用&#xff0c;賬號&#xff1a;openEuler 密碼&#xff1a;openEuler)…

vue使用tailwindcss

安裝依賴 pnpm add -D tailwindcss postcss autoprefixer創建配置文件tailwind.config.js npx tailwindcss init在配置文件content中添加所有模板文件的路徑 /** type {import(tailwindcss).Config} */ export default {content: [./index.html, ./src/**/*.{vue,js,ts,jsx,…

【Linux】開發工具入門指南,輕松掌握你的開發利器

開發工具 1. 軟件包管理器yum1.1 軟件包安裝方式1.2 yum的"三板斧"1.3 yum的周邊 2. 開發工具3. 編輯器vim4. 編譯器gcc、g5. 項目自動化構建工具make、Makefile6. 進度條小程序7. 調試器gdb 1. 軟件包管理器yum 1.1 軟件包安裝方式 源代碼安裝&#xff1a;用戶手動…

微信小程序 npm構建+vant-weaap安裝

微信小程序&#xff1a;工具-npm構建 報錯 解決&#xff1a; 1、新建miniprogram文件后&#xff0c;直接進入到miniprogram目錄&#xff0c;再次執行下面兩個命令&#xff0c;然后再構建npm成功 npm init -y npm install express&#xff08;Node js后端Express開發&#xff…

智慧校園的機遇與挑戰

隨著5G、物聯網、大數據等技能的日漸老練&#xff0c;數字化正在滲透到各行各業中&#xff0c;為事務立異和價值增加供給支撐。在教育職業&#xff0c;運用智能化體系賦能教育辦理越來越受歡迎&#xff0c;教育信息化方針一再出臺&#xff0c;進一步加快了智慧校園落地的腳步。…

Linux - 文件管理高級 sed

3.處理字符 sed ① sed 默認情況下不會修改原文件內容 ② sed 是一種非交互式的編輯器 3.1 工作原理 將原文件一行一行的進行處理&#xff0c;取出一行&#xff0c;放入“模式空間進行處理”&#xff0c;處理完成之后將結果輸出到屏幕上&#xff0c;然后讀取下一行&#xf…

彭濤 | 2024年5月小結

5月份還是蠻有刺激的&#xff0c;做了蠻多的事情&#xff0c;但是沒賺到錢&#xff0c;真是一屯操作猛如虎&#xff0c;一看賬戶0.5。 就喜歡創業這種一天天累死累活還不賺錢的感覺&#xff0c;哈哈哈哈 老規矩簡單說下這個月的情況&#xff0c;如果對你有收獲就最好了。 游學丹…

測繪外業需要注意些什么?

在進行測繪外業時&#xff0c;需要注意的事項涉及多個方面&#xff0c;包括充分的準備工作、合理的設備選擇、精確的操作技巧以及細致的數據處理。下面將具體展開這些要點&#xff1a; 1. 充分準備 - 了解任務要求&#xff1a;在開始外業工作前&#xff0c;需要明確測繪的目…

VUE框架前置知識總結

一、前言 在學習vue框架中&#xff0c;總是有些知識不是很熟悉&#xff0c;又不想系統的學習JS&#xff0c;因為學習成本太大了&#xff0c;所以用到什么知識就學習什么知識。此文檔就用于記錄零散的知識點。主要是還是針對與ES6規范的JS知識點。 以下實驗環境都是在windows環…

頭歌頁面置換算法第2關:計算OPT算法缺頁率

2 任務:OPT算法 2.1 任務描述 設計OPT頁面置換算法模擬程序:從鍵盤輸入訪問串。計算OPT算法在不同內存頁框數時的缺頁數和缺頁率。要求程序模擬駐留集變化過程,即能模擬頁框裝入與釋放過程。 2.2任務要求 輸入串長度作為總頁框數目,補充程序完成OPT算法。 2.3算法思路 OPT算…

【Tlias智能學習輔助系統】04 部門管理 刪除 和 新增

Tlias智能學習輔助系統 04 部門管理 刪除 和 新增 刪除部門APIDeptController.javaDeptService.javaDeptServiceImpl.javaDeptMapper.java前端聯調 新增部門API有一步簡化DeptController.javaDeptService.javaDeptServiceImpl.javaDeptMapper.java前端聯調 刪除部門API 請求路徑…