③【linux系統(tǒng)編程】李慧芹老師嵌入式Linux

linux_c系統(tǒng)開發(fā)學(xué)習(xí)筆記
day1

知識點匯總
要求:
1.學(xué)習(xí)的時候棄用root用戶
原因:root 和 普通用戶 區(qū)別很大
用普通用戶 和工作的時候更加相似、往往沒有權(quán)限,能學(xué)習(xí)到更多的東西。
2.重構(gòu)之前寫的代碼
用新學(xué)到的機制來解決舊的問題
老師會提醒哪里可以重構(gòu)、提升代碼量

標(biāo)準IO
I/O 是一切實現(xiàn)的基礎(chǔ)
沒有io就保存不了數(shù)據(jù)
分為 stdio sysio 即標(biāo)準IO和系統(tǒng)調(diào)用IO
如何區(qū)分?
系統(tǒng)調(diào)用io是系統(tǒng)內(nèi)核提供的,沒有統(tǒng)一的接口
所以需要標(biāo)準化,就有了標(biāo)準io
解放了程序員
比如printf 不用考慮環(huán)境和編譯器
所以優(yōu)先使用標(biāo)準io 移植性好 合并系統(tǒng)調(diào)用
加速讀寫(buffer cache)
標(biāo)準io:

例如fopen分別依賴open和openfile
apue第五章
標(biāo)準IO FILE類型
結(jié)構(gòu)體,有什么類型?
拿來主義,先知道用法,
man操作
多看man手冊

不能用指針去改常量,雖然有可能得到xbc
取決于編譯器會把它放在哪兒
errno error number 是個全局變量 出錯會將其值設(shè)置 如果不及時輸出就會被其他error覆蓋

gcc -E 編譯預(yù)處理指令
私有化數(shù)據(jù)? error不再是一個整型而是一個宏
制造打開失敗,用只讀的方式打開一個不存在的文件!
eof 指向的是文件最后一個有效字符的下一個位置
a+打開,讀的話在最前面,寫的話在最后面!
文本流、二進制流
b 是對Windows系統(tǒng)起作用
有可能移植的時候考慮加不加b(以二進制進行操作)
man 給出的頭文件有幾個就要包幾個
不能省略
沒包頭文件=編譯器看不見函數(shù)原型
gcc會把所有的返回值視為整型
能夠吧errno 轉(zhuǎn)換為 errno msg的函數(shù):
perror (char*)
輸出的內(nèi)容+ 錯誤信息(根據(jù)當(dāng)前的errno輸出錯誤信息)
strerror(int errnum)
在<string.h> 里面
提問:fopen打開文件,返回的指針?biāo)赶虻哪菈K內(nèi)存是屬于 棧?靜態(tài)區(qū)?還是堆?
我覺得是棧 但是不對,因為如果是棧上的內(nèi)存,函數(shù)結(jié)束后就會被釋放,返回了局部變量的地址!
靜態(tài)區(qū)也不對,雖然確實可以返回,但是用static只能有一塊,每次都會被覆蓋。
localtime 里面的指針放在靜態(tài)區(qū)
從fclose也可看出,在動態(tài)申請的內(nèi)存中能釋放(逆操作 可以判定是在動態(tài)申請)
沒有逆操作,則不一定在堆上
是資源有上限
一個進程能打開多少文件?
一個進程首先需要打開三個流
strin strout strerr
shell也打開了(應(yīng)該)輸出重定向 輸入重定向
ulimit -a 查看 open files 限制的個數(shù)
創(chuàng)建文件的時候沒有指定文件的權(quán)限!
如何指定?0666按位與 ~umask
~是取反 0開頭是八進制數(shù)
可以用來限制進程創(chuàng)建的文件權(quán)限
遇到函數(shù)返回了指針
要下意識的判斷 該指針指向靜態(tài)區(qū)、堆還是棧?
day2 2022年7月6日
fgetc fputc 操作字符輸入輸出
getchar 相當(dāng)于getc(stdin)
getc相當(dāng)于fgetc
int getc(FILE *stream);
返回的int是轉(zhuǎn)換過的
fgetc 和 getc 一模一樣?不對
fgetc是函數(shù) getc是宏
函數(shù)和宏的區(qū)別:
宏只占用編譯的時間,不占用調(diào)用的時間,函數(shù)則相反
linux內(nèi)核中用宏是為了節(jié)約時間
日常應(yīng)用級開發(fā)還是多用函數(shù)+inline
putchar (c) = putc(c,stdout)
putc 是宏, fputc是函數(shù)
小作業(yè):寫一個mycp 要求:復(fù)制出來的dest文件要跟src文件一模一樣。
寫程序的建議:
先寫幾個常用的頭文件stdio.h stdlib.h
文件關(guān)閉順序:
最好首先關(guān)閉依賴別人的對象
然后關(guān)閉被依賴的對象
只要用到命令行,就要判斷命令行的參數(shù)(相當(dāng)于寫注釋)并給出提示信息
內(nèi)存泄露:打開第一個文件成功,打開第二個文件失敗了
此時第一個文件還沒有關(guān)閉。
現(xiàn)在允許這種情況發(fā)生,以后會用到鉤子函數(shù)(?沒聽清)
可以把一旦出錯就運行的函數(shù)放在一起執(zhí)行
gets(char *s)
gets 很危險,不要用,因為s可能會溢出
推薦用
fgets (char *s,int size,F(xiàn)ILE *stream)
fgets 正常結(jié)束的情況:讀到size-1個字節(jié) (最后給s末位一個\0)和 讀到一個'\n'。
例如:用fgets(buffer,5,fp)讀取 abcd
需要讀兩次 第一次讀 abcd'\0' 第二次讀'\n''\0'
fgets出錯時,返回一個空指針
fputs(buffer,fp);將buffer的內(nèi)容輸出到指定流中
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream)
從stream中讀取 size * nmemb 大小的內(nèi)容到ptr指定的地方
size_t fwrite(const void *ptr, size_t size,size_t nmemb,FILE *stream)
從ptr指向的空間中讀取size*nmemb大小的內(nèi)容到stream中
問題:
1.錯位就全錯
2.返回值是成功讀到或者寫的對象個數(shù),不一定是字符的個數(shù)(即nmem)
可以當(dāng)做fgetc來使用
不能小瞧io操作,因為不熟悉io會導(dǎo)致后面的內(nèi)容難以理解
printf scanf
最好用fprintf,輸出重定向,比較有擴展性
sprintf 重定向到某一個字符串,比較像atoi的逆操作,但是沒有一個函數(shù)叫itoa
因為sprintf沒有指定緩沖區(qū)的大小,加了一個參數(shù),就有了snprintf,但是依舊沒有解決問題(最大字節(jié)數(shù)為size-1),因為后面的參數(shù)還是有可能造成緩沖區(qū)溢出。(fgets一樣有問題,不一定每次都能取滿)
scanf:從stdin以format格式取數(shù)據(jù),一個一個放到...里的參數(shù)去
fsancf 和 sscanf 都是重定向
sscanf:解析一個字符串把它里面的內(nèi)容按照format格式轉(zhuǎn)到...指定的位置
format里面最好不寫%s ?。。。ㄉ髦厥褂茫?/p>
因為你無法預(yù)測讀取的內(nèi)容有多長(可能溢出)這是scanf最大的缺陷之一
ftell 返回類型為long 很丑陋
因為不可能有文件指針有負數(shù)
但是為了遷就fseek(offset的參數(shù)類型long)
也就是說fseek和ftell在文件超過2g會失效
當(dāng)時一張軟盤16M 。。。所以。。
于是fseeko ftello 的參數(shù)變成了off_t(offset type)自己也可以使用這種技巧
off_t的大小一般來說是32位,但也不一定
這時可以利用宏可重復(fù)定義的特性,自己定義一下
#define _FILE_OFFSET_BITS 64
就可以吧off_t 轉(zhuǎn)換為64位的類型
gcc指令中也可以定義,makefile 也可以定義
CFLAGS+=-D_FILE_OFFSET_BITS=64
man手冊
CONFORMING TO 當(dāng)前函數(shù)所遵循的協(xié)議或標(biāo)準
可以看到fseeko是一個方言(不遵循c86或者c99 而是 posix)

而fseek是

那么當(dāng)文件比較大,而又要求移植性比較好,就得另辟蹊徑了。
fseek(FILE *fp,long offset,int whence) whence是起始位置,有SEEK_SET ,SEEK_CUR SEKK
rewind封裝了fseek
相當(dāng)于fseek(FILE *fp,0L,SEEK_SET);
fseek的妙用:產(chǎn)生空洞文件
例子:在使用迅雷等P2P下載的時候,下載的文件的大小不是從零開始的,而是一開始經(jīng)過了像fseek這樣的函數(shù),創(chuàng)建了一個大小與下載完成后的文件一樣的空洞文件,然后把該文件分塊,使用多線程或者多進程異步下載!
fflush()不刷新可能有意外發(fā)生
例子:

這樣無法打印出Before while()
標(biāo)準輸出是行緩沖模式,加上\n或者fflush才能輸出
fflush不帶參數(shù)時會刷新所有打開的流的緩沖區(qū),帶參數(shù)則刷新指定的流
緩沖區(qū)的作用:

修改緩沖區(qū) setvbuf
little trick:
在vim的命令行模式下
shift+k可以直接跳到man手冊上(coooool !)

問題:如何提取出完整的一行?
無論其大小
兩個選項
1.使用動態(tài)內(nèi)存來解決
2.getline
man getline

getline使用之前 需要宏定義一個_GNU_SOURCE
為什么要定義?為什么宏定義沒有替換的值?
經(jīng)過google發(fā)現(xiàn),_GNU_SOURCE相當(dāng)于一個開關(guān),用來讓用戶配置編譯環(huán)境的頭文件,這個宏可以讓用戶打開所有feature.
/* If _GNU_SOURCE was defined by the user, turn on all the other features.?*/
#ifdef _GNU_SOURCE
# undef?_ISOC99_SOURCE
# define _ISOC99_SOURCE?1
# undef?_POSIX_SOURCE
# define _POSIX_SOURCE?1
# undef?_POSIX_C_SOURCE
# define _POSIX_C_SOURCE?200112L
# undef?_XOPEN_SOURCE
# define _XOPEN_SOURCE?600
# undef?_XOPEN_SOURCE_EXTENDED
# define _XOPEN_SOURCE_EXTENDED?1
# undef?_LARGEFILE64_SOURCE
# define _LARGEFILE64_SOURCE?1
# undef?_BSD_SOURCE
# define _BSD_SOURCE?1
# undef?_SVID_SOURCE
# define _SVID_SOURCE?1
# undef?_ATFILE_SOURCE
# define _ATFILE_SOURCE?1
#endif
最好寫到makefile 當(dāng)中
練習(xí):自己封裝一個getline,實現(xiàn)讀一行
臨時文件
char* tmpnam(char *s)給一個字符地址,根據(jù)其中的內(nèi)容返回一個臨時文件名
在并發(fā)執(zhí)行的時候容易出錯,因為獲取文件名和創(chuàng)建文件不是一氣呵成的
FILE *tmpfile(void)
可以產(chǎn)生匿名文件,沒有名字就不會沖突(w+b方式打開)
一個文件,如果沒有任何的硬鏈接指向他,而當(dāng)前他的打開文件計數(shù)已經(jīng)成為零值,那么這個文件就會被銷毀。
所以如果fclose匿名文件的指針,那么該文件就被釋放了(如果沒有釋放且是守護進程就發(fā)生了泄漏)

還有其他方法創(chuàng)建臨時文件
day3 系統(tǒng)調(diào)用io
文件描述符在文件io中貫穿始終(fd)
上一個標(biāo)準io是FILE 指針
什么是文件描述符?
有哪些操作?
系統(tǒng)io的操作是支持標(biāo)準io的
fopen ->open等
本章知識點匯總

inode:文件的唯一標(biāo)識
文件描述符優(yōu)先使用當(dāng)前空閑的最小的整型值
(能夠復(fù)用之前用過的下標(biāo))
文件描述符的產(chǎn)生:
調(diào)用open函數(shù)

open函數(shù)需要包含以上三個文件
這里給出了兩種不同參數(shù)列表的open
提問:是用函數(shù)重載實現(xiàn)的?還是可變參數(shù)列表實現(xiàn)的?
應(yīng)該是可變參數(shù)列表,首先c語言中不能實現(xiàn)函數(shù)重載。若在C++環(huán)境中提問,則可以隨便傳幾個參數(shù),看他報錯的類型是參數(shù)類型error還是警告。
參數(shù)flags 是權(quán)限信息,由位圖實現(xiàn)。
他必須包含O_RDONLY O_WRONLY O_RDWR中的一個
更多的選項例如
文件的創(chuàng)建選項(creation flags)
文件的狀態(tài)選項(status flags)
可以通過按位或來進行選擇

O_CREAT,有則清空無則創(chuàng)建
O_TRUNC,截斷或者截短(truncate)
。。。有很多記不過來了
open返回值:成功返回fd失敗返回-1
day4
練習(xí):用系統(tǒng)io實現(xiàn)cp命令(重點,每次write不一定會全部寫入到fd中,要檢查寫入的個數(shù)和buffer的長度是否一致,不一致需要重復(fù)寫入)

為什么 第一個O_WRONLY和O_CREAT或運算了而后面的O_TRUNC是另外的參數(shù)?
應(yīng)該是寫錯了
文件IO和標(biāo)準IO的區(qū)別
標(biāo)準io是有緩沖的,吞吐量比較大(緩沖機制合并了系統(tǒng)調(diào)用)
文件io是實時的,響應(yīng)速度快。
如何使一個程序變快?
要區(qū)分是響應(yīng)速度還是吞吐量
標(biāo)準io和文件io不能混用??!
fileno可以轉(zhuǎn)換FILE* fp到 fd
fdopen可以把fd轉(zhuǎn)換為FILE *fp
為什么不能混用?至少pos不一樣!因為緩沖區(qū)會造成影響,fp的pos移動了,但是fd可能還沒來得及改動pos。反過來也是一樣,從fd中讀取一個cache塊大小,一下子讀出1024,但是fp中的pos可能才用到1個,并沒有加到1024
strace 跟蹤可執(zhí)行文件的系統(tǒng)調(diào)用
io效率問題
問題:bufsize的大小有何影響?
time命令 測試程序運行時間
練習(xí),把之前寫的cp的bufsize從128一直到16M測試他的時間,觀察在哪個點效率最高
不一定16M,直到段錯誤
可以看到,我用 truncate -s 2G test 命令創(chuàng)建了一個大小為2G的文件。

嘗試將其復(fù)制
用時20.739秒 此時的BUFSIZE為128
將BUFSIZE改為1024之后再次復(fù)制

用時減少到了4.228s??!
有沒有可能再減少一點呢?
我改成了8K的緩沖

2.56s! 依舊可以加速程序運行
128K: (和8K速度幾乎一樣)

似乎從8K~1M都差不多
16M會比較慢
區(qū)間應(yīng)該在8K到128K
32K

對于2G大小的文件
緩沖區(qū)大概設(shè)置為32k比較合適
這個文件緩沖比的大小為2^8
不知道這個結(jié)論有沒有普遍性?
后面發(fā)現(xiàn)沒有。。。(艸)
因為最佳效率在緩沖區(qū)為block的整數(shù)倍才會發(fā)生
可以用
stat / | grep "IO Block"
來查看當(dāng)前block的大小,我這邊服務(wù)器的block size為4K,這就是為什么在8K的時候效率會上升,且128K與8K差不多。(因為都是4K的整數(shù)倍?。?/p>
文件共享:
面試題:寫程序刪除一個文件的第十行
普通思路:打開文件一次,先找第11行,復(fù)制到第十行,像數(shù)組一樣依次覆蓋直到文件結(jié)束
這樣一次循環(huán)需要四次系統(tǒng)調(diào)用:seek
進階思路:打開文件兩次,一個只讀一個寫,
減少系統(tǒng)調(diào)用的次數(shù)
可以選用兩個進程還是兩個線程來進行這樣的操作
truncate/ftruncate函數(shù)截斷文件
作業(yè):實現(xiàn)上述功能
程序中的重定向:dup和dup2
duplicate 重復(fù),復(fù)制
int dup(oldfd)
close(1)和dup(fd)不是一個原子操作
也就是說,close1之后有可能其他人把1占用了
dup 的fd有可能不生成在1這個位置上,文件重定向就失敗了。
這里需要用到dup2這個原子操作
int dup2(oldfd , newfd)

dup2 (fd,1);dup2把第一個oldfd的描述符復(fù)制到第二個上,如果第二個被占用了,則會被關(guān)閉后復(fù)制。
老師寫的程序依然不對!
因為不能默認一個進程打開了012
而且有一個原則:不要以為自己在寫main函數(shù)
你總會與別人共同工作,在你修改了標(biāo)準流之后應(yīng)該還原回去。
day5
同步:
sync 同步內(nèi)核層面的buf和cache
解除設(shè)備掛載的時候,需要同步一下
fsync同步一個文件的buf和cache
fdatasync 只刷新數(shù)據(jù)不刷新亞數(shù)據(jù)(比如說文件的修改時間,文件的屬性)
fcntl(int fd,int cmd,...) :管理文件描述符
cmd可以選:F_DUPFD 。。。
cmd不同,返回值不同
管家級別的函數(shù)
dup和dup2封裝了fcntl
ioctl:設(shè)備相關(guān)的管家
一切皆文件好不好?
簡化了絕大數(shù)的操作,但是有些設(shè)備不僅僅是讀寫,損害了一小部分的利益
ioctl_list 居多,古董級別
/dev/fd/ 當(dāng)前運行進程的文件描述符目錄,是虛目錄。
文件系統(tǒng)
一、目錄和文件

獲取文件屬性:實現(xiàn)一個myls,盡力去模仿ls
問題:為什么有了短格式的選項還要有長格式?
因為短格式只能使用一次,有可能沖突
格式這類運維考的多
例如創(chuàng)建一個 文件名為-a的文件

兩種方法, -- (終止命令選項)或者指定路徑
myls可做選項 a i n l (uid gid)
不要求上色
這兩個文件可以找到uid和gid對應(yīng)的username和groupname,從而實現(xiàn)-n 和 -l

stat系統(tǒng)調(diào)用
fstat lstat (link stat)
獲取文件的屬性信息,填入buf中
復(fù)習(xí):FILE*和fd互相轉(zhuǎn)換的函數(shù)
FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);
函數(shù)原型

tags工具:vim -t查看類型的詳情

off_t的位數(shù)是16?32?64?
不知道,所以不能直接用int代替
使用makefile 進行宏的重定義
例如

文件占的磁盤空間 blocksize*block數(shù)
并不等于文件的字節(jié)數(shù)
這里5G大小的文件只占用磁盤4K!

空洞文件:
cp支持空洞文件拷貝
cp拷貝的時候會檢查緩沖區(qū),如果全是空則記錄長度
文件類型 dcb-lsp
文件類型:
-:普通文件 (regular file)
d:目錄文件(directory)
b:塊設(shè)備文件 (block device)
c:字符設(shè)備文件 (character)
l:符號鏈接文件(symbolic link file)
p:命名管道文件(named pipe)
s:套接字文件(socket)

文件屬性的確定: 0666 & ~umask
umask為了防止產(chǎn)生權(quán)限過松的文件
文件權(quán)限的更改 chmod

粘住位 (t位?)
是給一個可執(zhí)行文件設(shè)計的,加速其再次裝載
現(xiàn)在因為有cache 所以一般給目錄設(shè)置
比如tmp目錄

文件系統(tǒng):
FAT和UFS 不開源和開源
FAT :靜態(tài)單鏈表,
輕量級,目前U盤還在用,并不難,相當(dāng)于本科學(xué)期期末作業(yè)
二、系統(tǒng)數(shù)據(jù)文件和信息
三、進程環(huán)境