本文主要是介绍[1 Unix基础],希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.2 Unix体系架构
内核的接口被称为系统调用(system call)。公用函数库构建在系统调用接口之上,应用程序即可使用公用函数库,也可使用系统调用。shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。
图1-1 Unix操作系统的体系结构
1.3 登录
系统在口令文件(/etc/passwd)中查看登录名。口令文件的登录项由7个以冒号分隔的字段组成,依次是:登录名,加密口令,数字用户ID(205),数字组ID(105),注释字段,起始目录(/home/sar)以及shell程序(/bin/ksh)。
sar:x:205:105:Stephen:/home/sar:/bin/ksh
系统已经将加密口令(x)移动到了另一个文件中,第6章将说明这种文件以及访问它们的函数。
1.4 文件和目录
1 文件系统
stat和fstat函数返回包含所有文件属性的一个信息结构,第4章将详细说明。
2 文件名
只有斜线(/)和空字符不能出现在文件名中。斜线用来分隔构成路径名的各文件名,空字符则用来终止一个文件名。
3 目录
列出一个目录中所有文件的名字,ls(1):1表示后面接1个参数
#include "apue.h"
#include <dirent.h>int main(int argc, char *argv[])
{DIR *dp;struct dirent *dirp;if (argc != 2)err_quit("usage: ls directory_name");if ((dp = opendir(argv[1])) == NULL)err_sys("can't open %s", argv[1]);while ((dirp = readdir(dp)) != NULL)printf("%s\n", dirp->d_name);closedir(dp);exit(0);
}
dirent.h,使用opendir和readdir函数原型,以及dirent结构的定义。
exit,0表示正常结束,参数值1~255表示出错。
1.5 输入和输出
1 文件描述符
文件描述符通常是小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个现有文件或创建一个新文件时,它都返回一个文件描述符。
2 标准输入、标准输出和标准错误
每当运行一个程序,shell为其打开3个文件描述符,即标准输入、标准输出和标准错误。默认,这3个描述符都链接向终端。shell可以使这3个描述符重新定向到某个文件:
ls > file
标准输出重定向到file文件。
3 不带缓冲的I/O
函数open、read、write、lseek以及close提供了不带缓冲的I/O。
从标准输入读,并向标准输出写。程序用于复制任一Unix文件,类似cat。
#include "apue.h"#define BUFFSIZE 4096int main(void)
{int n;char buf[BUFFSIZE];while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)if (write(STDOUT_FILENO, buf, n) != n)err_sys("write error");if (n < 0)err_sys("read error");exit(0);
}
头文件<unistd.h>包含read和write函数声明,STDIN_FILENO(0),STDOUT_FILENO(1)。
read函数返回读取的字节数。当到达输入文件的末尾时,read返回0,程序停止运行。如果发生了一个读错误,read返回-1。
./mycat > data
标准输入是终端,标准输出重定向到文件data,标准错误是终端。该程序将用户输入的各行复制到标准输出,输入文件结束符(ctrl+d)后终止。
./mycat < infile > outfile
标准输入重定向到infile,标准输出重定向到outfile。该程序会将infile文件的内容复制到outfile文件中。
4 标准I/O
标准I/O为那些不带缓冲的I/O提供了带缓冲的接口。使用标准I/O无需考虑如何选取最佳的缓冲区大小,如3中的BUFFSIZE;同时简化了对输入行的处理,例如,fgets函数读取一行而read函数读取指定字节数。
将标准输入复制到标准输出,也能复制任一Unix文件:
#include "apue.h"int main(void)
{int c;while ((c = getc(stdin)) != EOF)if (putc(c, stdout) == EOF)err_sys("output error");if (ferror(stdin))err_sys("input error");exit(0);
}
getc一次读取一个字符,putc将此字符写到标准输出。读到输入的最后一个字节时,getc返回常量EOF。<stdio.h>中包含stdin,stdout和EOF(通常为-1)的定义。
1.6 程序和进程
内核使用exec函数(7个exec函数之一),将程序读入内存,并执行程序。
3 进程控制
有3个用于进程控制功能的主要函数:fork、exec和waitpid。
该程序从标准输入读取命令,并执行命令。类似shell。
#include "apue.h"
#include <sys/wait.h>int main(void)
{char buf[MAXLINE]; /* from apue.h */pid_t pid;int status;printf("%% "); /* print prompt (printf requires %% to print %) */while (fgets(buf, MAXLINE, stdin) != NULL) {if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = 0; /* replace newline with null */printf("parent getpid:%ld \n", (long)getpid());if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) { /* child */printf("child pid:%d \n", pid);printf("child getpid:%ld \n", (long)getpid());execlp(buf, buf, (char *)0);// todoerr_ret("couldn't execute: %s getpid:%ld", buf, getpid());exit(127);}printf("parent wait for pid:%d \n", pid);/* parent */if ((pid = waitpid(pid, &status, 0)) < 0)err_sys("waitpid error");printf("%% ");}exit(0);
}
子进程正常退出后todo后两句没有运行,可能的解释:
1 正常情况,子进程execlp里exit(0)会退出进程。
2 异常情况,子进程没有exit(0),需要用户exit(>0)表示异常退出。
解释如上程序:
1 用标准I/O函数fgets从标准输入一次读取一行。当输入文件结束符(ctrl+d)时,fgets返回null,循环停止。
2 fgets返回的每一行是以换行符终止,需要用null替换。这是因为execlp函数要求的参数是以null结束的而不是以换行符结束的。
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
其中第一个参数file指向可执行文件名称,因为execlp()函数会从系统PATH路径中寻找相应命令,所以不需要带完整路径;第二个参数之后就都是传给可执行文件的参数,类似main函数的args[],只是最后一个参数必须为空字符指针。
3 fork创建一个新进程,被调用一次(被父进程),返回两次(在父进程和子进程中)。
4 在子进程中,调用execlp以执行从标准输入读入的命令。这就用新的程序文件替换了子进程原来执行的程序文件。
5 父进程通过waitpid来等待子进程终止,参数指定要等待的进程(pid参数是子进程pid)。
6 该程序的限制是不能想执行的命令传递参数。
1.7 出错处理
Unix系统函数出错时,通常会返回一个负值,且整形变量errno通常被设置为具有特定信息的值。例如,open函数如果成功返回一个非负文件描述符,如果错误返回-1。open出错时,有大约15种errno值(文件不存在,权限问题等)。
linux错误码:
linux错误码大全_zhangwentaohh的专栏-CSDN博客_linux错误码
errno应注意两条规则。
1 如果没有出错,其值不会被清除。因此,仅当函数的返回值指明出错时,才检验其值。
2 任何函数不会将errno设置为0,且<errno.h>中定义的错误码都不为0。(一般>0)
C标准定义了两个函数,用于打印出错信息:
1 strerror
#include <string.h>// 返回值:指向消息字符串的指针
char *strerror(int errnum);
strerror函数将errnum映射为一个出错消息字符串。
todo:返回的char*堆内存何时free???贴下strerror源码:
/***
*char *strerror(errnum) - Map error number to error message string.
*
*Purpose:
* The strerror runtime takes an error number for input and
* returns the corresponding error message string. This routine
* conforms to the ANSI standard interface.
*
*Entry:
* int errnum - Integer error number (corresponding to an errno value).
*
*Exit:
* char * - Strerror returns a pointer to the error message string.
* This string is internal to the strerror routine (i.e., not supplied
* by the user).
*
*Exceptions:
* None.
*
*******************************************************************************/ #ifdef _UNICODE
wchar_t * cdecl _wcserror(
#else /* _UNICODE */
char * __cdecl strerror (
#endif /* _UNICODE */ int errnum )
{ _TCHAR *errmsg; _ptiddata ptd = _getptd_noexit(); if (!ptd) return _T("Visual C++ CRT: Not enough memory to complete call to strerror."); if ( (ptd->_terrmsg == NULL) && ((ptd->_terrmsg = _calloc_crt(_ERRMSGLEN_, sizeof(_TCHAR))) == NULL) ) return _T("Visual C++ CRT: Not enough memory to complete call to strerror."); else errmsg = ptd->_terrmsg; #ifdef _UNICODE _ERRCHECK(mbstowcs_s(NULL, errmsg, _ERRMSGLEN_, _get_sys_err_msg(errnum), _ERRMSGLEN_ - 1));
#else /* _UNICODE */ _ERRCHECK(strcpy_s(errmsg, _ERRMSGLEN_, _get_sys_err_msg(errnum)));
#endif /* _UNICODE */ return(errmsg);
}
2 perror
#include <stdio.h>void perror(const char *msg);
perror先输出msg指向的字符串,再基于errno的当前值在标准错误上输出一条出错信息。
说明两个出错函数的使用方法:
#include "apue.h"
#include <errno.h>int main(int argc, char *argv[])
{fprintf(stderr, "EACCES: %s\n", strerror(EACCES));errno = ENOENT;/* errno = EACCES; */perror(argv[0]);exit(0);
}测试结果:
EACCES: Permission denied
./testerror: No such file or directory
注意:将程序名(argv[0])作为参数传递给perror。这是使用惯例。使用这种方法,在程序作为管道时,就可以知道是哪个程序产生了出错信息了:
prog1 | prog2 | prog3
1.8 用户标识
3 附属组ID
/etc/group文件可以看出某个组里包含哪些用户,参考:
linux添加用户到附属组无权访问_麻麻兔的博客-CSDN博客
Unix系统是允许一个用户属于多个组的。
1.9 信号
信号用于通知进程发生了某种情况。例如,若某一进程执行除法操作,若除数为0则将名为SIGFPE(浮点异常)的信号发送给该进程。进程有以下3种处理信号的方式:
1 忽略信号。
2 按照系统默认方式处理。如除数0,系统默认方式是终止进程。
3 提供一个函数,信号发生时调用该函数,即捕捉信号。
产生信号:
1 中断键ctrl+c,对应信号SIGINT,系统默认动作是终止进程。
2 退出键ctrl+\
3 kill函数,在一个进程中调用此函数可以向另一个进程发送信号。
如下程序,为了能捕捉信号,程序调用signal函数,其中指定了当产生SIGINT信号时要调用的函数的名字sig_int。
#include "apue.h"
#include <sys/wait.h>static void sig_int(int); /* our signal-catching function */int main(void)
{char buf[MAXLINE]; /* from apue.h */pid_t pid;int status;if (signal(SIGINT, sig_int) == SIG_ERR)err_sys("signal error");printf("%% "); /* print prompt (printf requires %% to print %) */while (fgets(buf, MAXLINE, stdin) != NULL) {if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = 0; /* replace newline with null */if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) { /* child */execlp(buf, buf, (char *)0);err_ret("couldn't execute: %s", buf);exit(127);}/* parent */if ((pid = waitpid(pid, &status, 0)) < 0)err_sys("waitpid error");printf("%% ");}exit(0);
}void sig_int(int signo)
{// 有问题printf("interrupt\n%% ");
}
注意:信号处理函数里应该只能调用可重入函数。printf是不可重入函数,不应这么写,这里只是个demo。
可重入函数:可以在它还没有返回就再次被调用的函数。
不可重入函数:不可以在它还没有返回就再次被调用的函数。
1.10 时间值
当度量一个进程的执行时间时,Unix系统为一个进程维护了3个进程时间:
1 时钟时间:是进程运行的时间总量
2 用户CPU时间:执行用户指令所用的时间
3 系统CPU时间:为该程序执行内核程序的时间
例如,测试top命令的进程时间:
time -p top测试结果:
real 4.39
user 0.00
sys 0.01
1.11 系统调用和库函数
Unix实现提供了直接进入内核的入口点,这些入口点被称为系统调用(system call)。
《Unix程序员手册》的第2部分说明了系统调用接口,第3部分说明了通用库函数。
下图显示了应用程序、malloc函数和sbrk系统调用之间的关系:
可以看到,sbrk系统调用分配了一块内存给进程,并将虚拟地址空间映射到内存供malloc使用,而库函数malloc则在用户层次管理这一内存空间。
系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。如:
1 sbrk与malloc
2 不带缓冲的I/O与标准I/O
3 进程控制系统调用(fork、exec和wait)与system、popen
这篇关于[1 Unix基础]的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!