操作系统上机考试复习
文件相关
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个文件描述符表,各不相同
打开文件过程
- 找到文件对应索引节点inode
- 分配一个file结构体,结构体中inode字段指向该文件inode,结构体文件访问位置字段初始化为0
- 从文件描述符表中找到一个空闲项,指向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