操作系统上机考试复习

操作系统上机考试复习


文件相关

1.打开文件
#include <sys/stat.h>
#include <fcntl.h>

int open(const char* pathname, int flag);
//pathname:文件路径
//flags:打开参数

打开已有文件。

返回值:失败返回-1,成功返回>=0,并作为文件描述符

flags打开参数详情
O_RDONLY只读
O_WRONLY只写
O_RDWR读写
O_APPEND追加
O_TRUNC存在则刷新
O_CREAT不存在则创建
2.创建文件
#include <sys/stat.h>
#include <fcntl.h>

int create(const char* pathname, mode_t mode);
int open(const char* pathname, int flags, mode_t mode);
//mode:新建文件的访问模式,例如0777

如果文件存在,打开文件,如果文件不存在,创建文件。

返回值:失败返回-1,成功返回>=0,并作为文件描述符

3.关闭文件
#include <unistd.h>

int close(int fd);
//fd:文件描述符

将打开的文件关闭。

4.读取文件
#include <unistd.h>

int read(int fd, void* buf, size_t count);
//buf:内存缓冲区的起始位置
//count:内存缓冲区的长度,read返回值<=count

从打开的文件读取数据到内存缓冲区。

返回值:读取失败返回-1,读取成功返回实际读取的字节个数,返回0表示读取到文件尾

5.写入文件
#include <unistd.h>

int write(int fd, void* buf, size_t count);

从内存缓冲区把数据写入打开的文件。

返回值:写入失败返回-1,写入成功返回实际写入的字节个数

6.定位文件
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
//offset:相对于whence的位置偏移量
//whence:取的基准访问位置

调整文件的访问位置

位置详情
SEEK_SET文件开头,文件访问位置=offset
SEEK_CUR当前访问位置,文件访问位置=文件访问位置+offset
SEEK_END文件尾,文件访问位置=文件尾+offset

多进程

1.创建进程
进程定义

应用程序关于某数据集合上的一次运行活动,为OS进行资源分配调度基本单位

进程是动态执行过程,程序是静态的,同一程序可以对应的不同进程

每个进程拥有独立地址空间,包括代码段,数据段,堆栈段

进程之间的地址空间是隔离的,一个进程崩溃不会影响到另一个进程,也不会影响OS

进程属性

进程控制块(PCB):一个结构体,记录进程的各类属性

进程标识:每个进程有唯一ID,父进程ID

地址空间:包括代码段,数据段,堆栈段的起始地址和长度

打开文件列表:打开文件时,在打开文件列表中记录该文件信息,关闭时删除,进程终止时将尚未关闭的文件关闭

getpid原型
#include <sys/types.h>
#include <unistd.h>

pid_t getpid(); //获取当前进程ID
pid_t getppid(); //获取父进程ID
fork原型
#include <unistd.h>

pid_t fork();

创建一个子进程,父子进程并发运行。子进程复制父进程的代码段,数据段(父子拥有相同代码和数据),还有打开文件列表。父子的PID不同,且唯一。

返回值:父进程从fork返回处继续执行,返回的是子进程PID。子进程也从fork返回处继续执行,返回的是0

若要区分父子程序,可以使用PID判断

pid_t pid = fork();
if(pid == 0)
{
//子进程...
}
else
{
//父进程...
}
2.进程特性
并发

父子进程是并发运行的,输出结果会交织在一起

fork实现细节

OS为子进程创建PCB,将父进程的大部分属性复制到子进程的PCB中,不包括PID属性,为子进程创建地址空间,将父进程的代码和数据复制到子进程的地址空间中。

隔离特性

进程的地址空间相互隔离,每个进程拥有自己的地址空间,仅能访问自己的地址空间。

全局变量:存在于两个地址空间中,不是被共享,父子进程访问的都是自己的全局变量

3.装入程序
命令行参数
int main(int argc, char* argv[]);
//argc:命令行参数个数
//argv:命令行参数数组

命令名也被当作是参数,所以argc会比参数多1,argv[0]是命令名

execl原型
#include <unistd.h>

int execl(const char* path, const char* arg, ...);
//path:指定被装入程序的路径,可以是相对路径也可以是绝对路径
//参数个数可变,但最后一个参数必须是NULL

将当前进程的地址空间内容全部清空,再将path指定可执行程序的代码和数据装入当前进程的地址空间。

返回值:装入失败返回-1,装入成功则从被装入程序的main函数开始执行

例:execl("/bin/echo", "echo", "a", "b", "c", NULL);

execlp原型
#include <unistd.h>

int execlp(const char* file, const char* arg, ...);

功能与execl相同

区别:execlp中,指定路径还可以是PATH环境变量指定目录下的相对路径

execv原型
#include <unistd.h>

int execv(const char* path, const char* argv[]);
//argv:指定传递给程序的参数,为一个char指针数组,最后一项必须是NULL

功能与execl相同

例:char *argv[] = {"echo", "a", "b", "c", NULL};

execv("/bin/echo", argv);

execl的l指的是list,参数以列表的形式传递给可执行程序

execv的v指的是vector,参数以数组的形式传递给可执行程序

execvp原型
#include <unistd.h>

int execvp(const char* file, char* argv[]);
//多了一个p,意为path

与execv功能相同

4.退出程序
exit原型
#include <stdlib.h>

void exit(int status);
//status:退出状态标志

正常退出当前进程,将status&0xFF作为退出码返回给父进程

预定义常量
EXIT_SUCCESS为0的数值,表示正常退出
EXIT_FAILURE为非0的数值,表示程序执行过程发生了错误,异常退出

在linux shell中,可以通过特殊的环境变量$?获得程序的退出码

main函数中,return隐式调用exit

atexit原型
#include <stdlib.h>

int atexit(void (*function)(void));

注册一个回调函数function,进程正常结束时,function会被调用。如果注册多个回调函数,执行的顺序与注册的顺序相反。

5.等待进程
wait原型
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);
//status:如果不为NULL,子进程的退出码保存在status中

等待子进程结束

退出码

进程可能会由于不同的原因退出:主动调用exit,接受信号后退出

退出原因的宏功能
WIFEXITED(status)如果进程通过调用exit正常退出,则返回真
WEXITSTATUS(status)如果进程通过调用exit正常退出,则返回进程的退出码
WIFSIGNALED(status)如果进程接收信号后退出,则返回真
WTERMSIG(status)如果进程接收信号后退出,返回则导致进程退出的信号

文件描述符

1.概念
文件描述符

一个非负整数,用来描述文件,file description = fd

系统调用

open返回一个文件描述符,read和write都会用到

2.内核实现
索引节点

索引节点inode是unix文件系统中的一种数据结构

有文件的元信息数据:文件大小,磁盘位置,创建和修改时间,所有者,权限。

file结构体

内核使用file结构体表示一个被打开的文件

存放信息有:索引节点inode,当前访问位置,文件打开模式

文件描述符表

一个数组,数组类型是指针,指向file结构体,用于保存被打开的文件

内核打开文件时,分配一个file结构体表示被打开的文件,将该file结构体指针保存在文件描述符表中。

文件描述符表对进程来说是私有的,N个进程就有N个文件描述符表,各不相同

打开文件过程
  1. 找到文件对应索引节点inode
  2. 分配一个file结构体,结构体中inode字段指向该文件inode,结构体文件访问位置字段初始化为0
  3. 从文件描述符表中找到一个空闲项,指向file结构体,返回该空闲项在文件描述符表的下标,作为fd
进程控制块PCB

OS中表示进程状态的数据结构,存放用于描述进程情况和控制进程运行所需全部信息:

进程标识,处理机状态,进程调度信息,打开文件列表

标准输入输出
简介

每个进程执行,会自动打开三个标准文件:

fd=0 标准输入,fd=1 标准输出,fd=1 标准错误输出

新打开的文件从fd=3开始

描述符继承
fork系统调用
#include <unistd.h>

pid_t fork();

创建一个子进程,为子进程创建独立地址空间和独立文件描述符表,复制父进程代码段,数据段和文件描述符表内容。

系统调用dup
dup原型
#include <unistd.h>

int dup(int oldfd);
//oldfd:原文件描述符

通过复制文件描述符oldfd,创建一个新的文件描述符newfd,指向同一个文件

返回值:成功返回新复制的文件描述符,失败返回-1

dup2原型
#include <unistd.h>

int dup2(int oldfd, int newfd);
//oldfd:被复制的文件描述符
//newfd:新创建的文件描述符

通过复制文件描述符oldfd,创建新文件描述符newfd,指向同一个文件

返回值:返回新复制的文件描述符,失败返回-1

系统调用pipe
pipe原型
#include <unistd.h>

int pipe(int fd[2]);
//fd[0]:管道读端
//fd[1]:管道写端

创建一个可读写的管道,具备读端和写端

例子:使用管道运行cat /etc/passwd | wc -l

#include <stdio.h>
#include <unistd.h>

int main()
{
    int pid;    
    int fd[2];
    char buf[32];

    pipe(fd); 
    pid = fork();
    if (pid == 0) { 
        // child
        dup2(fd[1], 1); 
        close(fd[0]);
        close(fd[1]);

        execlp("cat", "cat", "/etc/passwd", NULL);
        exit(0);
    }
    // parent
    dup2(fd[0], 0); 
    close(fd[0]);
    close(fd[1]);
 
    execlp("wc", "wc", "-l", NULL);
    printf("Receive:%s\n", buf); 
    return 0;
}

多线程

1.创建线程
创建线程
#include <pthread.h>

int pthread_create(pthread_t* tid, pthread_attr, void* (*start_rountine)(void*), void* arg);
//tid:一个指针,用于保存新线程的ID
//attr:线程属性,为NULL使用缺省属性
//start_routine:函数指针,新线程从该指针指向的函数开始执行
//arg:提供给start_routine的参数

创建一个线程,新线程从start_routine开始执行,新ID保存在tid指向的位置

返回值:成功返回0,失败非0

线程参数
//为线程函数提供参数
void* arg = "hello";
pthread_create(&tid, NULL, start_routine, arg);

//线程函数
void* start_routine(void* arg)
{
  char* string = (char*)arg;
  puts(string);
}
参数类型

整型变量

int value = 123;
void* arg = (void*)&value;
pthread_create(&tid, NULL, start_routine, arg);

字符串变量

char* value = "string";
void* arg = (void*)value;
pthread_create(&tid, NULL, start_routine, arg);

结构体变量

struct person {
  char* name;
  int age;
}p;
void* arg = (void*)&p;
pthread_create(&tid, NULL, start_routine, arg);
2.等待线程
等待线程
#include <pthread.h>

int pthread_join(pthread_t tid, void** result);
//result:存放线程的计算结果

等待线程结束

返回值:成功返回0,失败返回非0

线程返回值

函数返回类型为void*

void* start_routine(void* arg)
{
	void* result;
	...
	return result;
}

等待线程返回结果

void* result;
pthread_join(tid, &result);
3.线程互斥
初始化互斥量
#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t* mutex, pthread_mutexattr_t* attr);
//初始化互斥量
//attr:初始化属性,为NULL使用默认
int pthread_mutex_destroy(pthread_mutex_t* mutex);
//释放互斥量

返回值:成功返回0,失败非0

加锁解锁
#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t* mutex);
//加锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);
//解锁

返回值:成功返回0,失败非0

4.条件变量
初始化
#include <pthread.h>

int pthread_cond_init(pthread_cond_t* cond, pthread_condattr_t* attr);
//初始化条件变量
//attr:若为NULL,使用缺省属性初始化
int pthread_cond_destroy(pthread_cond_t* cond);
//释放条件变量

返回值:成功返回0,失败返回非0

等待
#include <pthread.h>

int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
//cond:当前线程在条件变量上阻塞
//mutex:当前线程阻塞时所在的临界区

阻塞当前进程的运行

返回值:成功返回0,失败返回非0

唤醒线程
#include <pthread.h>

int pthread_cond_signal(pthread_cond_t* cond);
//唤醒阻塞在条件变量上的一个线程
int pthread_cond_broadcast(pthread_cond_t* cond);
//唤醒阻塞在条件变量上的所有线程

返回值:成功返回0,失败返回非0

共享缓冲区

使用条件变量实现生产者消费者问题

生产者:向共享缓冲区写入数据

消费者:从共享缓冲区读数据

使用条件变量

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define CAPACITY 4
//实际容纳CAPACITY-1
int buffer[CAPACITY];
//共享缓冲区
int in, out;
//读和写的指针

int buffer_is_empty()
{
  return in == out;
}

int buffer_is_full()
{
  return (in + 1)% CAPACITY == out;
}

int get_item()
{
  int item = buffer[out];
  out = (out+1) % CAPACITY;
  return item;
}

void put_item(int item)
{
  buffer[in] = item;
  in = (int+1)%CAPACITY;
}

pthread_mutex_t mutex;
//in/out互斥锁
pthread_cond_t wait_empty;
//生产者过快,等待buffer空
pthread_cond_t wait_full;
//消费者过快,等待buffer满

#define ITEM_COUNT (CAPACITY*2)
void* consumer(void* arg)
{
  int i, item;
  for(i=0; i<ITEM_COUNT; i++)
  {
    pthread_mutex_lock(&mutex);
    while(buffer_is_empty())
      pthread_cond_wait(&wait_full, &mutex);
    item = get_item();
    printf("cosume item: %c\n", item);
    
    pthread_cond_signal(&wait_empty);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}

void* producer(void* arg)
{
  int i, item;
  for(i=0; i<ITEM_COUNT; i++)
  {
    pthread_mutex_lock(&mutex);
    while(buffer_is_full())
      pthread_cond_wait(&wait_empty, &mutex);
    item = 'a' + i;
    put_item(item);
    printf("produce item: %c\n", item);
    
    pthread_cond_signal(&wait_full);
    pthread_mutex_unlock(&mutex);
  }
  return NULL;
}

int main()
{
  pthread_t consumer_tid;
  
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init(&wait_empty, NULL);
  pthread_cond_init(&wait_full, NULL);
  
  pthread_create(&consumer_tid, NULL, consume, NULL);
  producer(NULL); 
  
  return 0;
}

使用信号量

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

#define CAPACITY 4
int buffer[CAPACITY];
int in;
int out;
//-------------------------------------------//
typedef struct {
  int value;
  pthread_mutex_t mutex;
  pthread_cond_t cond;
}sema_t;

void sema_init(sema_t* sema, int value)
{
  sema->value = value;
  pthread_mutex_init(&sema->mutex, NULL);
  pthread_cond_init(&sema->cond, NULL);
}

void sema_wait(sema_t* sema)
{
  pthread_mutex_lock(&sema->mutex);
  while(sema->value <= 0)
    pthread_cond_wait(&sema->cond, &sema->mutex);
  sema->value--;
  pthread_mutex_unlock(&sema->mutex);
}

void sema_signal(sema_t* sema)
{
  pthread_mutex_lock(&sema->mutex);
  sema->value++;
  pthread_cond_signal(&sema->cond);
  pthread_mutex_unlock(&sema->mutex);
}
//---------------------------------------//

nt buffer_is_empty()
{
    return in == out;
}

int buffer_is_full()
{
    return (in + 1) % CAPACITY == out;
}

int get_item()
{
    int item;

    item = buffer[out];
    out = (out + 1) % CAPACITY;
    return item;
}

void put_item(int item)
{
    buffer[in] = item;
    in = (in + 1) % CAPACITY;
}

//===========================
sema_t mutex_sema;
sema_t empty_buffer_sema;
sema_t full_buffer_sema;
//===========================
#define ITEM_COUNT (CAPACITY*2)

void* comsume(void* arg)
{
  int i, item;
  for(i=0; i<ITEM_COUNT; i++)
  {
    sema_wait(&full_buffer_sema);
    sema_wait(&mutex_sema);
    
    item = get_item();
    prinntf("consume item: %c\n", item);
    
    sema_signal(&mutex_sema);
    sema_signal(&empty_buffer_sema);
  }
  return NULL;
}

void* produce(void* arg)
{
  int i, item;
  for(i=0; i<ITEM_COUNT; i++)
  {
    sema_wait(&empty_buffer_sema);
    sema_wait(&mutex_sema);
    
    item = 'a' + i;
    put_item(item);
    printf("produce item: %c\n", item);
    
    sema_signal(&mutex_sema);
    sema_signal(&full_buffer_sema);
  }
  return NULL;
}

int main()
{
  pthread_t consumer_tid;
  
  sema_init(&mutex_sema, 1);
  sema_init(&empty_buffer_sema, CAPACITY-1);
  sema_init(&full_buffer_sema, 0);
  
  pthread_create(&consumer_tid, NULL, consume, NULL);
  produce(NULL);
  pthread_join(consumer_tid, NULL);
  
  return 0;
}
5.关于条件变量
为什么条件变量是pthread_cond_wait(cond, mutex)而不是pthread_cond_wait(cond)

在执行pthread_cond_wait前,当前线程已经获得mutex,执行pthread_cond_wait时会阻塞,但在进入阻塞状态前必须释放已经获得的mutex,让其他线程才能进入临界区。

唤醒时,仍然处于临界区,必须再次获得mutex

所以参数列表需要带一个mutex

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值