1. 程式人生 > 其它 >Unix程式設計實踐教程筆記(二)使用者,檔案操作,聯機幫助,實現who,cp

Unix程式設計實踐教程筆記(二)使用者,檔案操作,聯機幫助,實現who,cp

命令也是程式

在unix中,將自己寫的程式的可執行檔案放到/bin,/usr/bin,/usr/local/bin任一目錄中即可增加新的命令

who命令

從第一列開始分別為:使用者名稱,終端名,登入時間

通過查詢man who,得知已登入使用者的資訊放在/var/run/utmp

使用man -k xxx可以根據關鍵字搜尋聯機幫助

由查詢可知,utmp檔案中儲存的是結構體陣列,陣列元素是utmp型別的結構

who命令的工作方式:

已登入的檔案資訊放在utmp中

who執行時,開啟utmp->讀取記錄->顯示記錄->關閉utmp檔案

所以需要從檔案中讀取一個整個資料結構

一次讀出整個資料結構(這裡是結構體)

使用getc是逐個位元組讀取

man -k file | grep read

-k選項只支援一個關鍵字

配合grep來查詢file相關的主題中與read相關的主題

一次讀取一個數據結構的方法:read系統呼叫

//read函式傳入一個檔案描述符,讀取檔案,將檔案中一定數目的位元組讀入一個緩衝區
//成功返回讀取到的位元組數  
//On  error, -1 is returned, and errno is set appropriately.
#include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);

使用open開啟檔案,獲取檔案描述符

       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

       int open(const char *pathname, int flags);

最後使用close系統呼叫關閉程序和檔案fd之間的連線

過濾使用者名稱為空和非真實使用者的條目:

當ut_type為7時,代表這是已經登入的使用者

顯示可讀的時間:

unix儲存時間使用的是time_t型別,時間用一個整數表示(long int)

       #include <time.h>

       char *asctime(const struct tm *tm);
       char *asctime_r(const struct tm *tm, char *buf);

       char *ctime(const time_t *timep);
       char *ctime_r(const time_t *timep, char *buf);

ctime將表示時間的整數值轉化為字串型別

#include<time.h>
#include<stdio.h>
#include<unistd.h>
#include<utmp.h>
#include<fcntl.h>
#include<stdlib.h>

void showtime(long timeval)
{	
	char* cp;
	cp = ctime(&timeval);
	printf("%12.12s",cp);
}
void show_info(struct utmp *utbufp)
{
	if(utbufp->ut_type!=USER_PROCESS)return;
	printf("%-8.8s",utbufp->ut_name);
	printf(" ");
	printf("%-8.8s",utbufp->ut_line);
	printf(" ");
	showtime(utbufp->ut_tv.tv_sec);
	if(utbufp->ut_host[0]!='\0')
	{
		printf("(%s)",utbufp->ut_host);

	}
	printf("\n");
}
int main()
{
	struct utmp utbuf;
	int fd;
	if((fd = open(UTMP_FILE,O_RDONLY))==-1){
		perror(UTMP_FILE);
		exit(1);
	}
	while(1)
	{
		if(read(fd,&utbuf,sizeof(utbuf))!=sizeof(utbuf))
		{
			break;
		}
		else{
			show_info(&utbuf);
		}

	}
	close(fd);
	return 0;
}

cp命令

   #include <sys/stat.h>
   #include <fcntl.h>

   int open(const char *pathname, int flags);
   int open(const char *pathname, int flags, mode_t mode);

   int creat(const char *pathname, mode_t mode);

通過讀寫來複制檔案

//create告知核心建立一個名為pathname的檔案,如果不存在則建立,存在則將內容情況,檔案長度設為0
//檔案許可位被設定為第二個引數指定的值
write函式返回寫入檔案的位元組數

從原始檔中讀取資料存入緩衝,將緩衝中的資料寫入目標檔案

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>

#define BUF 4096
#define MODE 0644

void print_errors(char *s1,char* s2)
{
    fprintf(stderr,"error:%s",s1);
    perror(s2);
    exit(1);
}
int main(int argc,char *argv[])
{
    int fd,out_fd,chars;
    char buf[BUF];
    if(argc!=3)
    {
        fprintf(stderr,"usage:%s source destination\n",argv[0]);
        exit(1);
    }
    if((fd=open(argv[1],O_RDONLY))==-1)
    {
        print_errors("can't open ",argv[1]);
    }
    
    if((out_fd=creat(argv[2],MODE))==-1)
    {
        print_errors("can't creat ",argv[2]);
    }
    while((chars=read(fd,buf,BUF))>0)
    {
        if(write(out_fd,buf,chars)!=chars)
        {
            print_errors("write error to ",argv[2]);
        }
    }
    if(chars==-1)
    {
        print_errors("read error from ",argv[1]);
    }
    if(close(fd)==-1||close(out_fd)==-1)
    {
        print_errors("error closing files","");
    }
    return 0;
}

使用快取提高檔案I/O效率

防止頻繁的系統呼叫

磁碟只能被核心訪問,系統呼叫時,要執行核心程式碼,系統呼叫結束時,CPU要切換回使用者模式,環境的切換花費很多時間

運用緩衝,對於who1.c:一次讀取多個記錄寫入緩衝中

當緩衝中所有記錄都被取走,才再次呼叫核心服務重新讀取資料

#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<utmp.h>
#include<fcntl.h>
#include<time.h>
#include<stdlib.h>
#define NRECS 16
#define UTSIZE (sizeof(struct utmp))

static char utmpbuf[NRECS*UTSIZE];

static int num_recs;
static int cur_rec;
static int fd = -1;



void showtime(long timeval)
{	
	char* cp;
	cp = ctime(&timeval);
	printf("%12.12s",cp);
}
void show_info(struct utmp *utbufp)
{
	if(utbufp->ut_type!=USER_PROCESS)return;
	printf("%-8.8s",utbufp->ut_name);
	printf(" ");
	printf("%-8.8s",utbufp->ut_line);
	printf(" ");
	showtime(utbufp->ut_tv.tv_sec);
	if(utbufp->ut_host[0]!='\0')
	{
		printf("(%s)",utbufp->ut_host);

	}
	printf("\n");
}

int utmp_open(char * filename)
{   
    fd = open(filename,O_RDONLY);
    cur_rec = num_recs = 0;
    return fd;
}

struct utmp* utmp_next()
{
    struct utmp* recp;
    if(fd==-1)
    {
        return NULL;
    }
    if((cur_rec==num_recs)&&utmp_load()==0)
    {
        return NULL;
    }
    recp = (struct utmp*)&utmpbuf[cur_rec*UTSIZE];
    cur_rec++;
    return recp;
}
//一次讀num_recs個,cur_rec用在從快取中取資料的時候來計數
int utmp_load()
{
    int amt_read;
    amt_read = read(fd,utmpbuf,NRECS*UTSIZE);
    num_recs = amt_read/UTSIZE;
    cur_rec = 0;    
    return num_recs;
}

void utmp_close()
{
    if(fd!=-1)
    {
        close(fd);
    }
}

int main()
{
    struct utmp* utbufp;
    struct utmp* ut = utmp_next();
    if(utmp_open(UTMP_FILE)==-1)
    {
        perror(UTMP_FILE);
        exit(1);
    }
    while((utbufp = utmp_next())!=NULL)
    {
        show_info(utbufp);
    }
    utmp_close();
    return 0;
}

核心緩衝技術

相比核心和使用者模式之間的切換,I/O更花時間

核心會將磁碟上的資料複製到核心緩衝區中,一個使用者空間中的程序要從磁碟讀取資料時,不直接讀磁碟

而是將核心緩衝區中的資料複製到程序的緩衝區中

檔案讀寫

登出過程的工作

開啟utmp->找到所在終端的登入記錄->修改記錄->關閉檔案
找到:while迴圈,每次讀入一條記錄,作比較

修改記錄:將ut_type改為DEAD_PROCESS,tv改為登出時間

使用系統呼叫lseek,(找到當前檔案位置的指標)

系統每次開啟一個檔案,都會儲存一個指向檔案當前位置的指標,當讀寫操作完成時,指標會移到下一個記錄位置
這個指標與檔案描述符相關聯。在這種情況下,指標是指向下一條登入記錄的頭一個位元組

lseek(fd,10*sizeof(struct utmp),SEEK_SET);//將指標指向第11個記錄的開始位置
lseek(fd,0,SEEK_END);//指向末尾
write(fd,"hello",strlen("hello"));//將一個字串寫入檔案末尾
lseek(fd,0,SEEKK_CUR);//返回指標指向的當前位置
//注意偏移量可以為負數
lseek(fd,-(sizeof(struct utmp)),SEEK_CUR);
//終端登出程式碼:
#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<utmp.h>
#include<fcntl.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>
int logout_tty(char *line)
{
    int fd;
    struct utmp rec;
    int len = sizeof(struct utmp);
    int retval = -1;
    if((fd=open(UTMP_FILE,O_RDWR))==-1)
        return -1;
    while(read(fd,&rec,len)==len)
    {
        if(strncmp(rec.ut_line,line,sizeof(rec.ut_line))==0)
        {
            rec.ut_type = DEAD_PROCESS;
            // if(time(&rec.ut_tv.tv_sec)!=-1)
            // {   
                //回到此記錄的開頭
                if(lseek(fd,-len,SEEK_CUR)!=-1)
                {
                    //更新
                    if(write(fd,&rec,len)==len)
                    {
                        retval=0;
                    }
                }
            // }
            break;
        }
    }
    if(close(fd)==-1)
        retval = -1;
    return retval;
}
int main(int argc,char *argv[])
{
    logout_tty(argv[1]);
    printf("%s\n",argv[1]);
    return 0;
}

系統呼叫中的錯誤處理

errno

用於確定發生什麼種類的錯誤

#include<errno.h>

perror

用於顯示錯誤資訊

#include<stdio.h>
#include<fcntl.h>
#include<sys/types.h>
#include<utmp.h>
#include<fcntl.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>

int main()
{
    int fd;
    fd = open("file",O_RDONLY);
    if(fd==-1)
    {
        perror("cannot open file");
    }
    return 0;
}