第一章 UNIX基础知识
1、文件和目录
1.1 文件系统
UNIX文件系统是目录和文件组成的一种层次结构,目录的起点称为根(root),其名字是一个字符/。
1.2 文件属性
文件属性是指文件类型(是普通文件还是目录)、文件大小、文件所有者、文件权限以及文件最后的修改时间等。Stat和fstat函数返回包含所有文件属性的一个信息结构。
struct stat {
unsigned long st_dev; /* Device. */
unsigned long st_ino; /* File serial number. */
unsigned int st_mode; /* File mode. */
unsigned int st_nlink; /* Link count. */
unsigned int st_uid; /* User ID of the file's owner. */
unsigned int st_gid; /* Group ID of the file's group. */
unsigned long st_rdev; /* Device number, if device. */
unsigned long __pad1;
long st_size; /* Size of file, in bytes. */
int st_blksize; /* Optimal block size for I/O. */
int __pad2;
long st_blocks; /* Number 512-byte blocks allocated. */
long st_atime; /* Time of last access. */
unsigned long st_atime_nsec;
long st_mtime; /* Time of last modification. */
unsigned long st_mtime_nsec;
long st_ctime; /* Time of last status change. */
unsigned long st_ctime_nsec;
unsigned int __unused4;
unsigned int __unused5;
};
1.3 .和..
创建新目录时会自动创建两个文件名:.(称为点)和..(称为点点),点指当前目录,点点指父目录,在最高层次的根目录中,点和点点相同。
1.4 路径名
以斜线开头的路径名称为绝对路径名,否则称为相对路径名。
1.5显示文件名:
自己实现ls命令
//myls.c
#include "apue.h"
#include <dirent.h>
int main( int argc,char *argv[] )
{
DIR *dp;
struct dirent *dirp;
if ( argc != 2 )
//printf("usage: myls directory_name");
err_quit("usage: myls directory_name");
if ( (dp = opendir(argv[1])) == NULL )
printf("can't open %s",argv[1]);
while ( (dirp = readdir(dp)) != NULL )
printf("%s\n",dirp->d_name);
closedir(dp);
exit(0);
}
在shell中运行gcc myls.c -o myls
运行结果:
myls没有把.和..过滤掉,而系统的ls把.和..过滤掉了,ls命令在打印目录项前一般按照字母顺序将名字排序。
1.6工作目录
每个进程都有一个工作目录,有时也称其为当前工作目录,所有相对路径名都从工作目录开始解释的,chdir函数更改其工作目录。
2、输入和输出
2.1 文件描述符
文件描述符通常是一个小的非负整数,内核用它标识一个特定进程正在访问的文件。当内核打开一个已有文件或者创建一个新文件时,它返回一个文件描述符。
2.2 open等函数
函数open、read、write、lseek以及close提供了不用缓冲的I/O。
3、程序和进程
3.1 进程ID
程序是存放在磁盘上、处于某个目录中的一个可执行文件;程序的执行实例被称为进程。Unix系统确保每个进程都有一个唯一的数字标识符,被称为进程ID,进程ID总是一个非负整数。
//mygetpid.c
//获得进程ID号
#include "apue.h"
int main(void)
{
printf("hello world form process ID %d\n",getpid());
exit(0);
}
运行结果:
3.2 进程控制
有三个用于进程控制的主要函数:fork、exec和waitpid。
自行简化实现一个简单的shell程序
#include "apue.h"
#include <sys/wait.h>
int main(void)
{
char buf[MAXLINE];//form "apue.h"
pid_t pid;
int status;
printf("%% ");//print '%'
while ( fgets(buf,MAXLINE,stdin) != NULL )
{
if ( buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = 0;
if ( (pid = fork()) < 0)
{
printf("fork error");
//err_sys("fork error");
}
else if (pid == 0)
{//child
execlp(buf,buf,(char *)0);
printf("couldn't execute:%s",buf);
//err_ret("couldn't execute:%s,buf");
exit(127);
}
//parent
if ((pid = waitpid(pid,&status,0)) < 0)
printf("waitpid error");//err_sys("waitpid error");
printf("%% ");//print '%'
}
exit(0);
}
运行结果:
说明:
>>fgets返回的每一行都以换行符终止,后随一个null字节,故用标准C函数strlen计算字符串长度,然后用以个null字节替换换行符,因为execlp函数要求参数以null而不是以换行符结束;
>>调用fork()创建一个新进程。新进程是调用进程的复制品,我们称调用进程为父进程,新创建的进程为子进程。fork向父进程返回新子进程的进程ID,对子进程则返回0。因为fork创建一个新进程,所以说它被调用一次(由父进程),但是返回两次。
>>在子进程中,调用execlp以执行从标准输入读入的命令。
>>子进程调用execlp执行新程序文件,而父进程希望等待子进程终止,这一要求由调用waitpid实现,其参数指定要等待的进程。
4、出错处理
当UNIX函数出错时,常常返回一个负值,而且整形变量errno通常呗设置含有附加信息的一个值。文件<errno.h>中定义了符号errno以及可以赋予它的各种常量,这些常量都是以E开头。
5、用户标识
5.1用户ID
口令文件登录项中的用户ID是个数值,它向系统标识各个不同的用户。用户ID为0的用户为根用户或者超级用户,通过getuid()获得。
//mygetpid.c
#include "apue.h"
int main(void)
{
printf("hello world form process ID %d\n",getpid());
exit(0);
}
运行结果:
5.2组ID
口令文件登录项也包括用户的组ID,它是一个数值,通过getgid()获得。
//mygetuid.c
#include "apue.h"
int main(void)
{
printf("uid = %d,gid = %d\n",getuid(),getgid());
exit(0);
}
运行结果:
6、信号
信号是通知进程已发生某种情况的一种技术。终端键盘上有两种产生信号的方法,分别是中断键(Delete或Ctrl+c)和退出键(Ctrl+\)。
7、时间值
长期以来,UNIX系统一直使用两种不同的时间值。
7.1日历时间
该值是自1970年1月1日00:00:00以来国际标准时间(UTC)所经过的秒数积累计值。基本数据类型是time_t。
7.2进程时间
这也被称为CPU时间,用以度量进程使用的中央处理机资源。基本数据类型是clock_t。
8、系统调用和库函数
各种版本的UNIX实现都提供定义明确、数量有限、可直接进入内核的入口点,这些入口点被称为系统调用。虽然有些函数可能会调用一个或多个内核的系统调用,但是它们不是内核的入口点。例如,printf函数会调用write系统调用以输出一个字符串,但是函数strcpy和atoi并不会使用任何系统调用。
==============================================================================
最近在读 Richard Stevens 的大作《UNIX环境高级编程》,相信很多初读此书的人都会与我一样遇到这个问题,编译书中的程序实例时会出现问题,提示 “错误:apue.h:没有那个文件或目录”。
apue.h 是作者自定义的一个头文件,并不是Unix/Linux系统自带的,此头文件包括了Unix程序所需的常用头文件及作者Richard自己写的出错处理函数。所以在默认情况下,gcc在编译时是读不到这个头文件的。
先在这个网站 http://www.apuebook.com/src.tar.gz 下载tar.gz格式的源码包,然后解压至某个目录,比如说/home/godsoul/下,然后进入目录apue.2e,把文件 Make.defines.linux 中的 WKDIR=/home/xxx/apue.2e 修改为 WKDIR=/home/godsoul/apue.2e ,然后再进入apue.2e目录下的std目录,打开linux.mk,将里面的nawk全部替换为awk,如果是用的vi/vim编辑器,可以使用这个 命令 :1.$s/nawk/awk/g (注意前面有冒号)
然后在此目录下运行make命令,即回到 /home/godsoul/apue.2e 目录在终端中输入 “./make” (不含引号)
然后把 /home/godsoul/apue.2e/inlcude 目录下的 apue.h 文件和位于 /home/godsoul/apue.2e/lib 目录下的 error.c 文件都复制到 /usr/include 目录下,apue.2e/lib/libapue.a 到/usr/lib/和 /usr/lib64下。注意复制这文件你需要有root权限。之所以要这样做,是因为gcc在链接头文件时会到 /usr/include 这个目录下寻找需要的头文件,若找不到则报错。
最终还要编辑一下复制过来的 apue.h 文件
在最后一行 #endif 前面添加一行 #include “error.c”
然后进入apue.2e/std 目录,编辑linux.mk。修改里面所有的nawk为awk。
这样就不会报错了。
====================================================================== ========