一,输入和输出
1.1 文件描述符:
通常是一个非负整数,内核用它以标识一个特定进程正在访问的文件。当内核打开一个现有文件或创建一个新文件时,都返回一个文件描述符
1.2 标准输入 标准输出 标准错误:
每当运行一个新程序时,所有的shell都为其打开3个文件描述符,即标准输入、标准输出和标准错误。这三个描述符都链接至终端。
在头文件#include <unistd.h>
中,定义了三个常量以表示标准输入、标准输出、标准错误。
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
# 使其中任何一个或所有这三个描述符都能重新定向到某个文件
ls > file.list # 将标准输出重定向到文件file.list
1.3 不带缓冲的I/O:
函数open、read、write、lseek、close等系统调用提供了不带缓冲的I/O。
ssize_t read(int fd, void *buf, size_t count);
1.4 带缓冲的I/O:
标准I/O库函数是带缓冲的函数,无需担心如何选取缓冲区的大小。
二,程序和进程
2.1 程序
程序是一个存储在磁盘上某个目录的可执行文件。内核使用exec函数将程序读入内存并执行程序
2.2 进程和进程ID
程序的一次执行实例称为进程。UNIX系统为每个进程都有一个唯一的数字标识符,即进程ID(PID),一个非负整数。在进程中可以通过getpid()
系统调用获取自身PID。
2.3 线程和线程ID
一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。
线程也用ID标识,但是线程ID只在它所属的进程内起作用。一个进程中的线程ID在另一个进程中没有意义。
三,出错处理
当UNIX系统调用出错时,通常会返回负值(有些返回的是空指针等),并且变量errno被设置为具有特定信息的值。
errno以前的定义是:
extern int errno;
但是在支持线程的环境中,多个线程共享进程地址空间,每个线程都有属于它自己的局部errno,以避免一个线程干扰另一个线程。因此errno定义为
extern int *__errno_location (void);
# define errno (*__errno_location ())
对于errno需要注意两点:
-
如果没有出错,errno值不会被例程清除。因此仅当函数返回值出错时,再检验errno值
-
任何函数都不会将errno设置为0
C标准提供两个函数以打印出错信息
- perror函数:
#include <stdio.h>
void perror (const char *msg);
先在标准错误上输出错误信息msg,然后是一个冒号一个空格,接着是errno对应的错误信息,然后换行符
- strerror函数:
#include <string.h>
char *strerror (int errnum);
将errnum(通常就是errno)对应的出错信息字符串返回。
#include "apue.h"
#include <errno.h>
int main(int argc,char* argv[]){
fprintf(stderr,"EACCES: %s\n",strerror(EACCES));
errno = ENOENT;
perror(argv[0]);
exit(0);
}
四,用户标识
4.1 用户ID
在口令文件的登录项中用户ID是一个数值,用来向系统标识不同的用户。
用户ID为0的用户为根用户(root)或超级用户(superuser),可以在口令文件中看到一个登录项的用户名是root。
root:x:0:0:root:/root:/bin/bash
这种用户的特权为超级用户特权,如果一个进程有超级用户特权,那么大多数文件权限检查都不再进行。
4.2 组ID
口令文件的登录项也包含组ID,也是一个数值。在口令文件中,有多个登录项拥有相同的组ID。
组ID用于将若干用户分配到不同的项目和部门中去,允许同组的各个成员之间共享资源(如文件)。比如可以设置文件权限为组内所有用户可以访问,组外用户不能访问。
组文件:*将组名映射为数值的组ID,通常是*/etc/group
$ cat /etc/group
> root:x:0:
> daemon:x:1:
对于磁盘上的每个文件,文件系统通常存储该文件所有者的用户ID和组ID,而不是用户名和组名。这是因为存储这两个ID只需要4个字节(假定每个ID以双字节整形值存放),而如果使用完整用户名和组名,需要更多的字节(需要更多磁盘空间)。另外在检验权限时,字符串比较与整数比较相比更慢。
但对于用户而言使用用户名和组名更方便,因此使用口令文件包含用户名和用户ID的映射,组文件包含组名和组ID的映射。比如ls -l
命令就使用口令文件将用户ID映射为用户名从而打印出文件所有者的用户名。
进程可以使用getuid()
和getgid()
获取用户ID与组ID。
组文件将组名映射为数值的组ID。组文件通常是/etc/group
#include "apue.h"
int main(void){
printf("uid = %d,gid = %d\n",getuid(),getgid());
exit(0);
}
4.3 附属组ID
大多数UNIX系统还允许一个用户属于另外一些组。允许一个用户属于最多16个其他的组。
可以通过etc/group中列有指定用户作为其成员的前16个记录项即为该用户的附属组。
adm:x:4:syslog # ID为4的组是syslog用户的附属组
/etc/group文件的每个记录项字段意义:
组名:加密口令:组ID:组中的附加用户
五, 信号
用于通知进程发生了某种情况。如执行除0操作,则将SIGFPE(浮点异常)信号发送给该进程。
进程有三种处理信号方式:
- 忽略信号(不推荐)
- 按系统默认方式执行,比如对于除0操作,系统默认方式是终止进程
- 提供信号处理函数,信号发生时调用该函数,称之为捕捉该信号。
六, 时间值
UNIX使用两种不同的时间值
6.1 日历时间
自1970年1月1日00:00:00起到某时刻的秒数,通过time_t
类型保存这种时间值。
7.2 进程时间
用于表示进程使用CPU时间资源。进程时间以时钟滴答计算,每秒钟曾经取为50,60,100个时钟滴答。通过clock_t类型保存这种时间值。
UNIX系统为每个系统维护了三个进程时间值
-
时钟时间:
进程运行的总时间。时钟时间 = 阻塞时间 + 就绪时间 + 运行时间。
-
用户CPU时间:
进程获得了CPU资源以后,在用户态执行的时间。
-
系统CPU时间:
进程获得了CPU资源以后,在内核态的执行时间
CPU时间 = 用户CPU时间 + 系统CPU时间
时钟时间又称为墙上时钟时间,它是进程运行的时间总量,其值与系统中同时运行的进程数有关。