首先感谢stevens等呕心沥血写出来的神书。谢谢,膜拜大神!以下为小弟学习神书的笔记。
1.5.输入输出
1.文件描述符:一个进程打开,或创建一个文件时,内核会分配一个非负整数标记该文件。对文件的读写就可以使用该文件描述符。
ls > test.text:将输出结果重新定位至文件。注ls将结果输出到屏幕
3、不带缓冲的i/o
open/read/write/lseek等用文件描述符操作。
#include"apue.h"
#define BUFFSIZE 1024
int 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>包含很多unix系统调用函数,也包括变量STDIN_FILENO与STDOUT_FILENO,它们指定了标准输入和输出的文件描述符。
如何输出文件不存在,shell创建它,
./a.out >data ,在屏幕中输入结果,将结果重定位输出到文件,以ctrl+D结束,不是ctrl+c。
./a.out <infile >outfile,将一个文件内容输出到另外一个文件
4.标准i/o
#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);
}
效果和不带缓冲的i/o一样
区别是,标准i/o不用指定复制文件的大小:fgets函数读取完整的行,read函数读取指定的字节数。
头文件stdio.h包含了所有标准i/o函数的原型。
getc一次读取一个字符,然后putc将字符标准输出,读到最后是getc返回常量EOF,值为-1
1.6程序与进程
1.程序
程序是磁盘上的可执行文件。内核使用exec函数,将程序读入内存,并执行程序。
2.进程和进程ID
进程是正在运行的程序,也可以用任务表示。
内核为每个进程分配唯一的数字标识符,成为进程ID,为一个非负整数
#include"apue.h"
int main(void)
{
printf("hello word from process ID %ld\n",(long)getpid());
exit(0);
}
函数getpid返回pid_t的数据类型。因为不知道具体数值大小故将它强制转换成长整型,用以printf输出。
3.进程控制
#include "apue.h"
#include <sys/wait.h>
int main(void)
{
char buf[MAXLINE];
pid_t pid;
int status;
printf("%%");
while (fgets(buf ,MAXLINE,stdin)!=NULL)
{
if (buf[strlen(buf)-1] == '\n')
buf[strlen(buf)-1] ='\0';
if ((pid =fork()) <0)
{
err_sys("fork error");
}
else if (pid ==0)
{
execlp(buf, buf, (char *)0);
err_ret("couldn't execute:%s", buf);
exit(127);
}
if((pid = waitpid(pid, &status, 0))<0)
err_sys("waitpid error");
printf("%%");
}
exit(0);
}
这里用到了exec函数族中的三个进程控制的函数:fork/exec/waitpid。理解了这三个函数的用法。该例子就不难了。
想要输入%自身,需要%%的形式
fgets一次读入一行,当键入ctrl+D时(文件结束符),循环结束
strlen计算字符长度,直到遇到\0.而sizeof计算所占内存的大小。
fgets每行以换行符结束,而execlp函数的参数要求以\0结束。故用0代替
fork函数,创建1个和父进程一模一样的子进程,通过进程号判断父进程/子进程、父进程中返回子进程的进程号,子进程中返回进程号为0.
创建不成功返回负值
子进程中用execlp函数执行新的程序文件,execlp第一个参数,即为输入的命令(会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件),第二个参数为argv[0]...第三个参数是字符型的空指针。命令执行成功则后面的命令不执行,否则继续执行后面的。
父程序中通过调用waitpid等待子程序的结束,其返回子进程的终止状态(status变量),第一个参数为要等待进程的进程号
4 线程和线程ID
多线程能够充分利用多处理器系统的并行能力
一个进程内所有的线程共享同一地址空间/文件描述符/栈/进程相关的属性。因此各线程访问共同的数据时需要采取同步措施避免不一致
线程也有ID的,但是它只能在所属的进程中起作用。对线程的处理时,需要使用线程ID使用
1.7 出错处理
unix系统函数出错时,通常会返回一个负值(整型变量erron),也有时会返回指向对象的指针,如返回一个NULL指针
对于返回值erron:第一:没有出错,其值不会被清除。第二返回值error不为0
c标准中定义2个函数,用于打印出错信息
char*strerror(int errnum);
将errnum,输出出错的字符串,返回字符串的指针
void perror(const char×msg);
输出msg指向的字符串: 对应error值的出错信息,换行符
#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);
}
stderr向屏幕输出
出错恢复:在error.h中分为致命性与非致命性。对于致命性的错误,无法执行恢复动作,最多在屏幕上打印一条出错信息。然后退出。
对于非致命性的错误,出错是暂时的。
用户表示
1 用户ID
用户ID在口令登录文件中是一个数值,用来表示不同的用户。用户不能更改自己的ID。ID为0的为根用户,或超级用户,登录名为root
mac os x客户端是不能打开超级用户的,仅能在服务器版本使用。
2 组ID
组ID也是由系统管理员在分配用户名时,分配的。多个用户ID具有相同的组ID。
同组中不同的成员允许共享资源。组文件会将组名映射为组ID。
检验用户权限时,字符串之间的比较比整型数更耗时间,但是对用户来说,使用名字更方便,所以口令文件包含登陆名与用户ID的映射关系。
#include "apue.h"
int main(void)
{
printf("uid = %d, gid = %d\n",getuid(), getgid());
exit(0);
}
getuid与getgid分别获取组ID与用户ID
3.附属组ID
1个用户可以属于多个附属组
1.9信号
信号用于通知进程发生了某种情况。
进程有3种处理信号的方式:
1.忽略信号:
2.按系统默认方式处理:终止进程
3.捕捉该信号:键盘上可以产生2种信号:中断键(ctrl+c或delete)退出键(ctrl+\),用于中断进程
第二种是:调用kill函数:在一个进程中调用此函数,可以向另一进程发送一个信号。使用这种方法只能以该进程拥有者或超级用户的方式。
捕捉信号调用signal函数。
#include "apue.h"
#include <sys/wait.h>
static void sig_int(int);
int main(void)
{
char buf[MAXLINE];
pid_t pid;
int status;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal error");
printf("%% ");
while (fgets(buf, MAXLINE, stdin) !=NULL)
{
if (buf[strlen(buf)-1] =='\n')
buf[strlen(buf) -1]=0;
if ((pid = fork()) <0)
{
err_sys("fork error");
}
else if (pid == 0)
{
execlp(buf, buf, (char*)0);
err_ret("couldn't execute:%s",buf);
exit(127);
}
if ((pid = waitpid(pid, &status ,0)) <0)
err_sys("waitpid error");
printf("%% ");
}
exit(0);
}
void sig_int(int signo)
{
printf("interrupt:%% ");
}
1.10时间值
unix系统使用2种不同的时间
日历时间:1970年1月1日 00:00:00以这个时间为基准。数据类型time_t用于保存时间值
进程时间:也成cpu时间,用于度量进程使用cpu资源的时间。进程时间以时钟滴答计算。每秒钟去50/60/100个时钟滴答
数据类型clock_t保存这种时间值。
衡量进程的执行时间的量:
始终时间:进程运行的时间总量,与系统同时运行的进程数有关
用户cpu时间:执行用户指令所用的时间量。
系统cpu时间:执行内核程序所需时间。用户cpu时间与系统cpu时间之和为cpu时间。
time+命令可以查看执行命令的时间
1.11系统调用和库函数
操作系统提供多种服务的入口点,这些入口点称为系统调用。unix为每个系统调用在标准c库中设置一个相同的名字,
用户用标准c调用这些函数。通用的库函数也可能调用内核函数。例如malloc调用sbrk系统调用实现自己的分配空间算法。
库函数与内核函数的职责不同,内核中的系统调用分配一块空间给进程,库函数malloc在用户层次管理这个空间。
unix只提供一个系统调用,返回自世界时1970年1月1日零时起所经过的描述。将其转换成适合本时区的时间,留给用户进程处理。
系统调用通常提供一个最小接口,库函数提供教复杂的功能