操作系统可定义为一种软件,它控制计算机硬件资源,又称之为内核(kernel)。
内核的接口被称为系统调用(system call)。公用函数库在系统调用之上,应用软件既可以使用公用函数库,也可以使用系统调用。(系统调用和库函数的比较在最后)
Figure 1.3. List all the files in a directory
#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);
}
Figure 1.4. List all the files in a directory
#include "apue.h"
#define BUFFSIZE 4096
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);
}
The constants STDIN_FILENO and STDOUT_FILENO are defined in
标准IO函数提供一种对不用缓冲IO的带缓冲接口。它有两个优点:
1) 无须担心如何选取最佳的缓冲区大小
2) 简化了对输入行的处理
The function getc reads one character at a time, and this character is written by putc. After the last byte of input has been read, getc returns the constant EOF (defined in
#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);
}
Figure 1.6. Print the process ID
#include "apue.h"
int main(void)
{
printf("hello world from process ID %d\n", getpid());
exit(0);
}
When this program runs, it calls the function getpid to obtain its process ID.
Figure 1.7. Read commands from standard input and execute them
#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 */
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 */
//waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。
if ((pid = waitpid(pid, &status, 0)) < 0)
err_sys("waitpid error");
printf("%% ");
}
exit(0);
}
We call fork to create a new process, which is a copy of the caller. We say that the caller is the parent and that the newly created process is the child. Then fork returns the non-negative process ID of the new child process to the parent, and returns 0 to the child. Because fork creates a new process, we say that it is called once by the parent but returns twice in the parent and in the child.
Fork()函数调用一次返回两次,一次是自己的进程ID,另外一个是子进程,是0
#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);
}
C标准定义了两个函数,他们帮助打印出错信息
#include<string.h>
char *strerror(int errnum);
//此函数将errnum(它通常就是errno值)映射为一个出错信息字符串,并且返回此字符串的指针。
//perror函数基于errno当前值,在标准出错上产生一条出错信息,然后返回
#include<stdio.h>
void perror(const char* msg);
//它首先输出由msg指向的字符串,然后是一个冒号,一个空格,接着是对应于errno值的出错信息,最后是一个换行符
#include "apue.h"
Int main(void)
{
printf("uid = %d, gid = %d\n", getuid(), getgid());
exit(0);
}
由于是root用户,所以uid是0.我也没有分配组,所以gid也是0
Figure 1.10. Read commands from standard input and execute them
#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%% "); }
信号是通知进程已发生某种情况的一种技术。例如,若某一进程执行除法操作,其除数为0,则将名为SIDFPE(浮点异常)发送给进程。进程如何处理有三种选择:
1.忽略该信号。有些信号表示硬件异常,例如除以0或者访问地址空间以外的单元,这些异常产生的后果不确定,所以不推荐
2.按系统默认方式处理。对于除以0,系统默认终止进程
3.提供一个信号,信号发生时则调用该函数,这被称为捕捉该信号。我们需要提供自编的函数来处理它
系统调用实际上就是指最底层的一个调用,在linux程序设计里面就是底层调用的意思。面向的是硬件。而库函数调用则面向的是应用开发的,相当于应用程序的api
1.系统调用
系统调用提供的函数如open, close, read, write, ioctl等,需包含头文件unistd.h.以write为例:其函数原型为 size_t write(int fd, const void *buf, size_t nbytes),其操作对象为文件描述符或文件句柄fd(file descriptor),要想写一个文件,必须先以可写权限用open系统调用打开一个文件,获得所打开文件的fd,例如 fd=open(\“/dev/video\”, O_RDWR)。fd是一个整型值,每新打开一个文件,所获得的fd为当前最大fd加1(Linux系统默认分配了3个文件描述符值:0-standard input,1-standard output,2-standard error)
系统调用通常用于底层文件访问(low-level file access),例如在驱动程序中对设备文件的直接访问。
系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。
2.库函数
标准C库函数提供的文件操作函数如fopen, fread, fwrite, fclose, fflush, fseek等,需包含头文件stdio.h.以fwrite为例,其函数原型为size_t fwrite(const void *buffer, size_t size, size_t item_num, FILE *pf),其操作对象为文件指针FILE *pf,要想写一个文件,必须先以可写权限用fopen函数打开一个文件,获得所打开文件的FILE结构指针pf,例如pf=fopen(\“~/proj/filename\”, \“w\”)。实际上,由于库函数对文件的操作最终是通过系统调用实现的,因此,每打开一个文件所获得的FILE结构指针都有一个内核空间的文件描述符fd与之对应。同样有相应的预定义的FILE指针:stdin-standard input,stdout-standard output,stderr-standard error.
库函数调用通常用于应用程序中对一般文件的访问。
库函数调用是系统无关的,因此可移植性好。
由于库函数调用是基于C库的,因此也就不可能用于内核空间的驱动程序中对设备的操作。
使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?
这是因为,读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。在用户空间和内核空间,对文件操作都使用了缓冲区,例如用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,才将用户缓冲区的内容写到内核缓冲区,同样的道理,当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。