操作系统的组成

  1. 操作系统分为内核态(kernel)和用户态(user)两层
    1. 内核态主要负责管理 cpu,内存等计算机资源,并向上层用户态应用提供服务
    2. 内核包含文件系统,访问控制等关键组件

image.png

Linux 操作系统

文件描述符

  1. 定义
    1. 内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。
    2. Linux 启动时会打开三个文件描述符 ,分别代号为 0,1,2,代号为 0 的是标准输入(STDIN_FILENO),代号为 1 的是标准输出(STDOUT_FILENO),代号为 2 标准错误输出(STDERR_FILENO)。
    3. 根据 Linux 一切皆文件的思想,标准输入,标准输出,标准错误输出均为文件。 这打开的三个文件分别是:/dev/stdin(标准输入文件),/dev/stdout(标准输出文件),/dev/stderr(标准出错文件)。
    4. 如果此时去打开一个新的文件,它的文件描述符会是 3,因为前三个默认被系统占用,POSIX 标准要求每次打开文件时必须使用当前进程中最小可用的文件描述符。

系统调用

处于用户态的程序不可以直接接触 cpu 等计算机资源,需要由用户态切换为内核态执行,所以系统调用则由用户态切换为内核态执行

常见的系统调用

  1. int fork(void);
  2. int exit(int) attribute((noreturn));
  3. int wait(int*);
  4. int pipe(int*);
  5. int write(int, const void*, int);
  6. int read(int, void*, int);
  7. int close(int);
  8. int kill(int);
  9. int exec(char*, char**);
  10. int open(const char*, int);
  11. int mknod(const char*, short, short);
  12. int unlink(const char*);
  13. int fstat(int fd, struct stat*);
  14. int link(const char*, const char*);
  15. int mkdir(const char*);
  16. int chdir(const char*);
  17. int dup(int);
  18. int getpid(void);
  19. char* sbrk(int);
  20. int sleep(int);
  21. int uptime(void);

lab

  1. copy
#include "kernel/types.h"
#include "user/user.h"
int main(){
        char buf[64];//创建缓冲区
        while(1){
        int n=read(0,buf,sizeof(buf));//读取标准输入文件的内容,到buf里,返回读取的字节数
        if(n<=0)break;
        write(1,buf,n);//将buf里的内容输出到标准输出文件
        }
        exit(0);
}
  1. open
#include "kernel/types.h"
#include "kernel/fcntl.h"
#include "user/user.h"

int main(){
        int fd=open("output.txt",O_WRONLY|O_CREATE);
        write(fd,"ooo\n",4);
        exit(0);
}

open 这个系统调用可以打开文件,并返回一个文件描述符,接受两个参数,第一个是一个字符型指针,表示文件名,第二个参数为打开方式。

#define O_RDONLY             00     /*只读方式打开*/
#define O_WRONLY             01     /*只写方式打开*/
#define O_RDWR               02     /*读写方式打开*/
  1. fork
#include "kernel/types.h"
#include "user/user.h"
int main(){
        int pid ,status;
        pid=fork();//此处有两个进程,父子进程的下一个语句都是if(pid==0)
        if(pid==0){
                char *argv[]={"echo","this","is","echo",0};
                exec("echo",argv);
                printf("exec failed\n");
                exit(1);
        }else{
                printf("parent waiting");
                wait(&status);
                printf("the child exite with status %d\n",status);
        }
        exit(0);

}

一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用 fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
为什么两个进程的 fpid 不同呢,这与 fork 函数的特性有关。fork 调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork 返回新创建子进程的进程 ID;
2)在子进程中,fork 返回 0;
3)如果出现错误,fork 返回一个负值;

在 fork 函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork 函数返回 0,在父进程中,fork 返回新创建子进程的进程 ID。我们可以通过 fork 返回的值来判断当前进程是子进程还是父进程。