LinuxDay01 檔案&程式基礎
0x01 檔案
1.檔案的基本操作
1.1檔案描述符號
C中對應檔案的是 檔案指標
C++中對應檔案的是檔案流物件
windows 中對應檔案的是 檔案控制程式碼
Linux 檔案描述符號(一個整數) 對應 檔案
標準輸入輸出裝置 在linux上也是檔案,他們的檔案描述符號分別為0,1,2
標準輸入裝置:0
標準輸出裝置:1
標準錯誤輸出裝置:2
Linux上開啟任何一個檔案,都會得到一個檔案描述符號,如果成功開啟,則檔案描述符號為正數
如果開啟失敗,則為負數
1.2檔案操作的函式
open
read
write
lseek
close
mmap 檔案內容對映
man open 檢視open的幫助文件,預設為檢視對應命令的幫助文件
引數 2 表示檢視標準C庫函式
標準庫函式必須加標頭檔案:<stdio.h>
man 2 函式名,例如 fopen fwrite fread這些C的函式
標準C庫函式與作業系統無關,因此程式的可移植性好
引數 3 表示檢視系統庫函式 man 3 open
系統庫函式必須加標頭檔案:<unistd.h> 即 unix std
以上提到的linux系統的檔案操作函式,都是系統庫函式,即使用者程式無權呼叫,
必須通過訪管指令,引起訪管中斷,使得CPU從目態進入管態,來呼叫這些系統庫函式
然後,從管態回到目態,繼續執行使用者程式
以上過程便是系統呼叫
因為open為linux的系統庫函式,所以用man 3 open去檢視幫助文件
int open(const char* path,int oflag,int mode);
/**
*開啟一個檔案
*返回該檔案的檔案描述符
*@const char* path 檔名
*@int oflag 檔案開啟方式
*@int mode 檔案許可權
*/
PS:Linux系統中%m通配標準的錯誤輸出
ssize_t pwrite (int fd, const void * buf, size_t count,off_t offset);
/**
*向檔案中寫入資料
*@int fd 檔案的描述符號
*@const void * buf 要寫入的資料的地址
*@size_t count 位元組數
*@off_t offset 偏移量
*/
ssize_t write (int fd, const void * buf, size_t count);
/**
*向檔案中寫入資料
*@int fd 檔案的描述符號
*@const void * buf 要寫入的資料的地址
*@size_t count 位元組數
*/
demo:
向檔案中寫入一個結構體:
如果用cat a.txt 檢視檔案中的內容,當然是亂碼
所以還必須寫一個程式從a.txt中將寫入的內容讀出來
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
struct Student{
char name[20];
int age;
double score;
};
int main(){
int fp = open("./a.txt",O_RDWR | O_CREAT,0777);
if(-1 == fp){
printf("開啟檔案失敗:%m\n");
return -1;
}
printf("建立檔案成功!\n");
struct Student s = {"Brad",20,88.88};
write(fp,&s,sizeof(s));
close(fp);
return 0;
}
從檔案中讀取結構體:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
struct Student{
char name[20];
int age;
double score;
};
int main(){
int fd = open("./a.txt",O_RDWR|O_EXCL,0777);
if(-1 == fd){
printf("開啟檔案失敗:%m\n");
}
printf("開啟檔案成功!\n");
struct Student* p = (struct Student*)malloc(sizeof(struct Student));
read(fd,p,sizeof(struct Student));
printf("%s %d %.2lf\n",p->name,p->age,p->score);
return 0;
}
exit(-1)結束一個程式
//開啟a.bat 每隔3個字元列印一個字元到終端
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
int main(){
int fd = open("./a.bat",O_RDONLY | O_EXCL,0666);
if(-1 == fd){
printf("開啟失敗:%m\n");
exit(-1);
}
printf("開啟成功!\n");
int r;
char c;
while(1){
r = read(fd,&c,1);
if(r>0){
printf("%c\n",c);
}else{
break;
}
lseek(fd,3,SEEK_CUR);
}
close(fd);
return 0;
}
2.檔案鎖
linux 系統是多工多使用者的,所以可能兩個程式在同時操作同一個檔案
#檔案鎖的分類:
1.lockf 加建議性鎖
即防君子不防小人,只是在檔案上做了一個標記,表示該檔案有程式在操作。
其他程式要動這個檔案,看一下如果有標記,最好就不要動,但是如果想動的話還是可以動。
2.fcntl 加強制性鎖 並且 加建議性鎖 並且 加記錄鎖
#fnctl函式的使用
#include <fcntl.h>
int
fcntl(int fildes, int cmd, ...);
/**
*@int fildes 檔案描述符
*@int cmd 操作命令,如果操作命令為F_SETLK F_GETLK 第三個引數為鎖的資訊結構體
*@struct flock{
l_type, 鎖的型別(F_RDLCK讀鎖/共享鎖 即多個程式可以同時讀,F_WRLCK寫鎖/互斥鎖即多個程式不能同時寫一個檔案)
l_whence, 加鎖的起始位置 建議為SEEK_SET,即檔案頭
l_start, 相對偏移量,建議為0
l_len, 加鎖的長度
l_pid 加鎖的程式id 誰持有這把鎖
}
*/
獲取檔案上鎖的情況:(Mac上總是獲取失敗)
// 判斷a.bat是否有加鎖,如果加了,判斷是什麼鎖
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(){
int fd = open("./a.bat",O_RDWR | O_EXCL,0666);
if(-1 == fd){
printf("開啟檔案失敗:%m\n");
exit(-1);
}
printf("開啟檔案成功!\n");
struct flock lock = {0};
lock.l_type = 0;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 15;
// 獲取a.bat的鎖的資訊,儲存到lock結構體中
int ret = fcntl(fd,F_GETLK,&lock);
if(-1 == ret){
printf("獲取鎖情況失敗:%m \n");
close(fd);
exit(-1);
}
printf("獲取鎖情況成功!");
if(lock.l_type != F_UNLCK){//如果已經上了鎖
if(F_RDLCK == lock.l_type){
printf("當前檔案上了一把讀鎖");
}
if(F_WRLCK == lock.l_type){
printf("當前檔案上了一把寫鎖");
}
printf("上鎖的程式的id為:%d\n",lock.l_pid);
}else{
printf("當前檔案沒有上鎖");
}
close(fd);
return 0;
}
給檔案加一個讀鎖:
// 給a.bat加一把讀鎖,然後收到控制資訊後,解除鎖
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(){
int fd = open("./a.bat",O_RDWR | O_EXCL,0666);
if(-1 == fd){
printf("開啟檔案失敗:%m\n");
exit(-1);
}
printf("開啟檔案成功!\n");
struct flock lock = {0};
// 設定讀鎖
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 15;
int ret;
ret = fcntl(fd,F_SETLK,&lock);
if(-1 == ret){
printf("上讀鎖失敗:%m\n");
close(fd);
exit(-1);
}
printf("上讀鎖成功!\n");
getchar();
//解除讀鎖
lock.l_type = F_UNLCK;
ret = fcntl(fd,F_SETLK,&lock);
if(-1 == ret){
printf("解除讀鎖失敗:%m\n");
close(fd);
exit(-1);
}
printf("解除讀鎖成功!\n");
return 0;
}
給檔案加上一個寫鎖:
// 給a.bat加一把寫鎖,收到控制資訊後解除鎖
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
int main(){
int fd = open("./a.bat",O_RDWR | O_EXCL,0666);
if(-1 == fd){
printf("開啟檔案失敗:%m\n");
exit(-1);
}
printf("開啟檔案成功!\n");
struct flock lock = {0};
// 設定寫鎖
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 15;
int ret;
ret = fcntl(fd,F_SETLK,&lock);
if(-1 == ret){
printf("上寫鎖失敗:%m\n");
close(fd);
exit(-1);
}
printf("上寫鎖成功!\n");
getchar();
//解除讀鎖
lock.l_type = F_UNLCK;
ret = fcntl(fd,F_SETLK,&lock);
if(-1 == ret){
printf("解除寫鎖失敗:%m\n");
close(fd);
exit(-1);
}
printf("解除寫鎖成功!\n");
return 0;
}
3.io多路複用
以後講
0x02 程式
#程式的組成:
1.資料:全域性變數,已經初始化的靜態變數,只讀變數,位初始化的靜態變數
2.程式碼:程式的原始碼
3.堆疊:一個程式有獨屬於它的堆和棧,例如:普通變數,手動記憶體分配
#程式的模式:
使用者模式(目態)
核心模式(管態)
#程式的執行:
方式一:手動執行
方式二:排程程式,例如:中斷,調整程式優先順序
ps top 檢視程式 ps -all 檢視所有程式
nice renice 調整程式的優先順序
kill -9 程式id 結束程式
crontab 定時任務
bg 將前臺程式變成後臺程式
#init程式
Linux下所有程式都是init程式的子程式
所有程式資源都在 /proc 目錄下
獲取程式id和父程式id
#include <unistd.h>
#include <stdio.h>
int main(){
printf("當前程式id:%d\n",getpid());
printf("當前程式的父程式的id:%d\n",getppid());
while(1);
return 0;
}
#建立程式的方式
fork:以克隆clone(系統庫函式)當前程式的方式來建立一個子程式
子程式擁有父程式一樣的資源和程式上下文
vfork:父子程式共享同一塊實體記憶體
copy-on-write 寫時拷貝
exec函式族:
execl execv execle execlp execvp
fork建立程式:
當父程式執行到fork函式時,會拷貝出一份和自己完全一樣的子程式
也從fork函式開始執行,之不過父程式fork函式的返回值大於0
子程式fork函式的返回值等於0
#include <stdio.h>
#include <unistd.h>
int main(){
printf("這是父程式,當前程式id:%d\n",getpid());
for(int i = 0;i<5;i++){
printf("run ----\n");
sleep(1);
}
int ret;
ret = fork();
if(-1 == ret){
printf("fork函式執行出錯!\n");
}else if(ret > 0 ){
printf("這是父程式:%d\n",getpid());
while(1){
printf("------\n");
sleep(1);
}
}else{
printf("這是子程式:%d,其父程式id:%d",getpid(),getppid());
while(1){
printf("-----\n");
sleep(1);
}
}
return 0;
}
#程式的結束:
exit: 釋放程式佔據的資源後再呼叫_exit; exit(退出碼)
_exit: 直接結束程式,不管程式佔據的資源有沒有釋放
#殭屍程式:
如果父程式先於子程式結束,那麼子程式結束後,其佔據的資源就可以無人釋放,子
程式就變成了殭屍程式。如何避免殭屍程式呢?
最好讓父程式晚於子程式結束,如果不能讓父程式晚於子程式結束
那麼就子程式不傳送SIGCHILD訊號
wait(接收退出碼的地址):子程式結束的時候會向父程式傳送一個訊號,wait函式就是一直阻塞直到等這個訊號。
返回值為 子程式的pid
waitpid:
SIGCHILD:子程式結束前會向父程式傳送的訊號
SIGHUP:子程式掛起的時候向父程式傳送的訊號
#程式組:
一個程式組中有多個程式,一個程式組有一個組長,一般為父程式
#會話:
一個會話中有多個程式組
#守護程式:
守護程式是一個獨立的程式,用來記錄作業系統的執行情況(用來記日誌檔案)
檢視當前系統上的守護程式:
ps axj
tpgid為-1的是守護程式
一般來說系統中重要的程式都有其守護程式,來為他寫日誌
一個會話中至少要有一個守護程式
##建立守護程式
1.把其父程式幹掉
2.建立新的會話,擺脫原有會話,原有程式組的控制
3.擺脫終端的控制:
方法一:關閉 0 1 2,即關閉標準輸入輸出裝置,讓終端無法控制他,例如無法在終端上通過ctrl c關閉掉守護程式
方法二:重定向 到 黑洞裝置 /dev/NULL
4.繼承自父程式的檔案描述符 要遮蔽掉,因為子程式是父程式的拷貝,所以在父程式建立子程式前開啟檔案獲得的
檔案描述符號,子程式也會擁有。因為守護程式並不需要這些檔案描述符,所以需要遮蔽掉。
##建立守護程式的程式設計模型
方式一:
1.建立新的會話,setsid,建立者自動成為新會話的組長
2.改變當前目錄 chdir 建議改變當前目錄為 / 根目錄,防止當前目錄被刪除
3.重設檔案許可權掩碼 umask
4.關閉標準輸入輸出裝置 close(0) close(1) close(2)
方式二:
0.重設檔案許可權掩碼 umask
1.建立子程式 fork
2.讓父程式結束
3.建立新的會話
4.防止子程式成為殭屍程式:忽略 SIGCHILD SIGHUP signal
5.更改當前工作目錄
6.重定向檔案描述符號 open 黑洞裝置 dup(fd,0) dup(fd,1)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(){
//讓子程式成為守護程式
//去除檔案許可權
umask(0);
if(fork()){
//結束父程式
printf("父程式結束!\n");
exit(0);
}else{
printf("當前程式id:%d,父程式id為:%d\n",getpid(),getppid());
//建立新的會話,讓子程式成為新的會話的組長
setsid();
//忽略SIGCHILD SIGHUP
signal(SIGCHLD,SIG_IGN);
signal(SIGHUP,SIG_IGN);
//修改當前工作目錄為 /
chdir("/");
//重定向標準輸入輸出裝置的檔案描述符 到 黑洞裝置
int fd = open("/dev/NULL",O_RDWR);
dup(0);
dup(1);
}
while(1){
//守護程式要做的事情
sleep(1);
}
return 0;
}
#程式間通訊:IPC Inter Process Communication
分類:
1.父子程式之間通訊
2.同一主機上的程式通訊
3.不同主機上的程式通訊(網路程式設計)
unix系統:
匿名管道 (pipe)
有名管道(FIFO)
訊號(signal)
System V 版本:
訊息佇列
共享記憶體
訊號量(旗語)
Posix 版本
訊息佇列
共享記憶體
訊號量
## 匿名管道pipe:
pipe 本質是一個我們看不到的檔案,但是有兩個檔案描述符號,一個檔案描述符號
用來讀,一個檔案描述符號用來寫。用於父子程式之間的通訊
pipe程式設計模型:
1.建立pipe描述符號
2.建立父子程式框架:先建立pipe,再建立父子程式,因為子程式是父程式所有資源的拷貝,所以子程式就會繼承父
程式的pipe的檔案描述符號
3.使用pipe通訊
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
int main(){
//1.建立pipe描述符號
int fd[2];
int r = pipe(fd);
if(-1 == r){
printf("建立pipe描述符號失敗:%m\n");
exit(-1);
}
printf("建立pipe描述符號成功!\n");
//2.建立子程式
if(fork()){
//父 讀
sleep(1);//讓子程式先寫
char buff[1024];
int r;
while(1){
//fd[0]專門用於讀
r = read(fd[0],buff,1023);
if(r>0){
buff[r]=0;
printf(">>%s\n",buff);
}
}
}else{
//子 寫
char buff[1024];
int n = 0;
while(1){
sprintf(buff,"父程式你好:%d\n",n++);
write(fd[1],buff,strlen(buff));
sleep(1);
}
}
}
## 有名管道 fifo緩衝區
有名管道 本質是一個使用者可以看到的檔案,一個具備先入先出特性的緩衝區
有名管道是單工的,即只能單方面工作,即只能一方寫或者一方讀,不能一方的同時一方在讀
有名管道程式設計模型:
1.建立管道 mkfifo
在程式A或者程式B中建立管道均可
-------------------------------------------------------------
程式A 程式B
2.開啟管道 2.開啟管道
3.使用管道 3.使用管道
4.關閉管道 4.關閉管道
---------------------------------------------------------------
5.刪除管道
注意:需要兩邊都開啟才能開啟,如果只有程式A開啟管道檔案,open函式會阻塞
只有讀寫兩個程式都開啟open才會結束
linux 命令 | 使用的就是管道,雖然該命令建立的管道檔案的位置我們不知道
fifoA.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(){
//1.建立有名管道
int r = mkfifo("./fifo.dat",0666);
printf("mkfifo %m\n");
//2.開啟有名管道
int fd = open("./fifo.dat",O_RDONLY);
if(-1 == fd){
printf("開啟管道失敗:%m\n");
unlink("./fifo.dat");
}
printf("開啟管道成功!");
//3.使用有名管道
char buff[1024];
while(1){
r = read(fd,buff,1023);
if(r>0){
buff[r]=0;
printf(">>%s\n",buff);
if(strcmp("exit1\n",buff) == 0) break;
}
}
//4.關閉有名管道
close(fd);
//5.刪除有名管道
unlink("fifo.dat");
return 0;
}
fifoB.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(){
//1.建立有名管道
int r = mkfifo("./fifo.dat",0666);
printf("mkfifo %m\n");
//2.開啟有名管道
int fd = open("./fifo.dat",O_RDWR);
if(-1 == fd){
printf("開啟管道失敗:%m\n");
unlink("./fifo.dat");
}
printf("開啟管道成功!");
//3.使用有名管道
char buff[1024];
while(1){
memset(buff,0,1024);
//向標準輸出裝置中寫 相當於printf("請輸入:")
write(1,"請輸入:",strlen("請輸入:"));
//從標準輸入裝置 0 去讀,相當於scanf
r = read(0,buff,1023);
if(r>0){
write(fd,buff,strlen(buff));
//往管道內寫入
if(0 == strcmp("exit1\n",buff)) break;
}
}
//4.關閉有名管道
close(fd);
printf("write fifo end!\n");
return 0;
}
#訊號:
使用者模式下 用來模擬 核心模式 下的中斷機制
訊號的本質:本質是個整數
訊號的分類:
1.不可靠訊號:非實時的 kill -l 可以去檢視有多少種訊號 kill 命令就是用來傳送訊號的 1-31是不
可靠訊號。 不可靠訊號 相當於佇列 訊息,可靠訊號相當於非佇列訊息
2.可靠訊號:實時傳送的 32-64
訊號產生 ---> 訊號傳送----> 訊號處理
以上過程能否被打斷(遮蔽)呢?
有些訊號能被遮蔽
例如:
使用signal()函式可以遮蔽掉這兩個訊號
SIGCHLD :子程式結束時傳送給父程式的訊號
SIGHUP:當關閉終端時,傳送給相關程式的訊號,該訊號一般會預設結束終端中的程式。
signal() 用來處理訊號,即註冊訊號處理函式或者忽略訊號
signaction() 高階訊號處理函式
ctrl+c 本質上是傳送一個SIGINT訊號給終端上的當前程式,Linux作業系統的程式排程機制會
自動處理該訊號。當然我們也可以自己手動處理該訊號,即如果我們註冊了訊號處理函式
那麼以上過程就會被打斷,訊號就會傳送到我們的訊號處理函式來處理
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
/**
*@int signum 我們要處理的訊號
*@sighandler_t handler 訊號處理函式
*/
signal.c
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void handler(int sigNum){
if(sigNum == SIGINT){
printf("接收到一個SIGNINT訊號!\n");
}
}
int main(){
int n = 0;
// 註冊一個訊號處理函式
signal(SIGINT,handler);
while(1){
printf("%d\n",n);
n++;
sleep(2);
}
return 0;
}
執行截圖:
當我們ctrl c的時候,就會中斷該程式,然後用呼叫我們註冊的訊號處理函式
sigaction
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
/**
*@int signum 要處理的訊號
*@const struct sigaction * act 處理方式
*@struct sigaction *oldact 老的處理方式
*/
結構體
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
//以上都是訊號處理函式
sigset_t sa_mask; //遮蔽方式
int sa_flags; //處理方式
void (*sa_restorer)(void);
}
有些訊號不能被遮蔽
例如:
SIGKILL 殺死程式的訊號 不能被遮蔽
SIGCOUT 讓以停止的程式繼續執行
1.01
相關文章
- Linux基礎學習——檔案基礎Linux
- PHP基礎---檔案包含PHP
- Oracle控制檔案基礎Oracle
- Unity基礎——.meta檔案Unity
- 檔案系統基礎
- 抖音小程式基礎之 小程式有哪些檔案構成
- 檔案管理基礎命令一
- python 基礎之檔案Python
- Python基礎——檔案操作Python
- Spark基礎-Scala檔案操作Spark
- Oracle引數檔案基礎Oracle
- 【Java基礎】--上傳檔案Java
- C++基礎::檔案流C++
- 檔案IO中基礎操作
- python基礎1 - 多檔案專案和程式碼規範Python
- 29-檔案物件基礎操作物件
- 檔案管理基礎命令之二
- BIOS/UEFI基礎——DSC檔案iOS
- mysql基礎概念之socket檔案MySql
- Oracle重做日誌檔案基礎Oracle
- Oracle基礎 09 概要檔案 profileOracle
- Python基礎 - 檔案拷貝Python
- linux檔案系統基礎Linux
- Python基礎 - 檔案和流Python
- Linux基礎之檔案管理Linux
- [基礎知識] Redis 配置檔案Redis
- Linux系統檔案系統及檔案基礎篇Linux
- Linux基礎命令---lp列印檔案Linux
- Linux基礎命令---lpr列印檔案Linux
- iOS逆向之旅(基礎篇) — Macho檔案iOSMac
- c#(解析xml檔案基礎方法)C#XML
- Linux基礎:檔案查詢findLinux
- linux檔案系統基礎(轉)Linux
- 【0基礎學爬蟲】爬蟲基礎之檔案儲存爬蟲
- shell基礎教程二十四: shell基礎教程: Shell檔案包含
- python 檔案操作的基礎總結Python
- Django基礎教程之配置檔案詳解Django
- FastAPI基礎之 表單和檔案操作ASTAPI