操作系统1实验复习
系统调用
程序接口就是系统调用
系统调用是通过中断机制实现的(通过软中断进入)
系统调用与一般调用的区别:
- 运行在不同系统状态。调用系统调用的程序在用户态,系统调用被调用的程序运行在内核态(系统态)
- 通过软中断进入。系统调用会涉及到系统状态的转换,因此需要执行访管指令(陷入指令)由软中断(陷入机制)转向对应的系统调用处理程序,同时CPU执行状态从用户态转换为系统态。
- 返回问题。通过中断返回指令退出,结束系统调用后,要分析原进程的优先级,如果有比它优先级更高的,则会将该进程放到就绪队列。
- 系统调用可以嵌套调用
特权指令:在系统态运行的指令。对内存空间的访问范围不受限制,可以访问用户空间和系统空间。只允许OS使用,不允许应用程序使用,避免引起混乱。
非特权指令:用户态运行的指令。应用程序使用的都是非特权指令,只能完成一般任务和操作,不能对系统中的硬件和软件进行直接访问,只能访问内存中的用户空间,避免应用程序运行异常而对系统产生破坏。
特权指令与非特权指令的限制是由硬件实现的。
- 内核态/系统态/管态:较高特权,执行一切指令,包括特权指令
- 用户态/目态:较低特权,只能运行应用程序、非特权指令
内核态->用户态:执行一条特权指令,修改PSW为用户态
用户态->内核态:由中断引发(硬件完成“变态”)
中断:使CPU由用户态变为内核态,使OS内核重新夺回对于CPU的控制权
中断类型:
- 内中断(软中断):与当前执行指令有关
- 异常 由 CPU 执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。
- 陷入 在用户程序中使用系统调用。
- 外中断:由 CPU 执行指令以外的事件引起
如 I/O 完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。
中断处理程序一定是内核程序,运行在内核态
进程管理
fork
参考资料
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。
fork之后,子进程会拷贝父进程的数据空间、堆和栈空间(实际上是采用写时复制技术),二者共享代码段。
所以在子进程中修改全局变量(局部变量,分配在堆上的内存同样也是)后,父进程的相同的全局变量不会改变。
wait
等待子进程结束
函数功能是: 父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
wait()函数成功返回等待子进程的pid,失败返回-1
exec
参考
调用exec函数之后,进程会被新程序替代,除了进程标识号不变,此外新进程复制的旧进程的其它信息(代码、数据、栈等)均被新程序的信息替代,新程序从自己的main开始运行。
- fork函数是用于创建一个子进程,该子进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个程序执行的方法。
- exec类函数共有6种,只有execve是系统调用,六种函数的参数各不相同,逐个分析
- ‘l’:list,列表,调用带有l的exec函数,需要将参数全部列出来注意l一定要带上NULL
- ‘v’:vector,数组,调用带有v的exec函数,需要将参数存到一个指针数组中,再把数组首地址给函数
- ‘p’:path,环境变量,调用带有p的exec函数,需要保证将要跳转到程序在环境变量PATH中(不在PATH中函数调用会失败)
- ‘e’:environment,环境变量
1
2
3
4
5
6#include <unistd.h>` //exec函数族的头文件
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
常用的是execlp
- execlp和execl的区别在于,execlp在第一个参数时候,不需要全路径,只需要写上执行命令的文件名即可,表示你需要执行谁,往后的参数也就是和execl的传参一样;
1 |
|
如果调用的execl,那么第一个ls就要换成/user/bin/ls
,之后的参数可以理解为传递一整个argv(argv[0]是程序名,之后是参数)
在使用exec函数族时,一定要加上错误判断语句。因为exec很容易执行失败,其中最常见的原因有:
- 找不到文件或路径,此时errno被设置为ENOENT。
- 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT。
- 没有对应可执行文件的运行权限,此时errno被设置为EACCES。
进程睡眠
头文件:#include <unistd.h>
定义函数:unsigned int sleep(unsigned int seconds);
函数说明:sleep()
会令目前的进程暂停, 直到达到参数seconds
所指定的时间, 或是被信号所中断.
返回值:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。
结束进程
三种正常结束,两种异常终止
正常:
- main函数return=调用exit
- 调用exit函数
- 调用_exit函数
异常终止: - 调用abort
- 进程收到特定信号
执行命令行
system()
(用fork,exec和waitpid三个系统调用实现的)
暂停当前进程
pause()暂停运行直至收到了某个信号为止
信号处理
signal
系统调用signal是进程用来设定某个信号的处理方法,系统调用kill是用来发送信号给指定进程的。这 两个调用可以形成信号的基本操作。
signal为指定信号安装新的处理句柄。可以自定义16、17号信号
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:
- signum表示要处理的信号类型
- handler:描述与信号相关的动作,有三种情况
- 一个处理函数,函数结束后,进程返回被中断的点继续执行
- SIG_IGN:忽略参数signum所指的信号。
- SIG_DFL:恢复参数signum所指信号的处理方法为默认值。
kill
系统调用kill用来向进程发送一个信号。该调用声明的格式如下:
int kill(pid_t pid, int sig);
参数:
- pid:
- 大于 0 的整数:表示发送信号给具有该 PID 的单个进程。
- 等于 0:表示发送信号给与调用进程属于同一进程组的所有进程。也就是调用kill函数的这个进程组的进程都会接受到这个信号。
- 等于 -1:表示发送信号给除了调用进程和 init 进程(PID 为 1)以外的所有进程。通常情况下,这需要调用进程拥有特定权限,例如 root 用户权限。
- 小于 -1 的整数:表示发送信号给进程组 ID 等于 pid 绝对值的所有进程。换句话说,如果 pid 是 -N(N > 1),则信号将发送给进程组 ID 为 N 的所有进程。
- sig:要发送的信号的编号
sig 参数可以是整数信号代码,也可以是预定义的信号常量
其中,SIGKILL(9):杀死信号,强制结束进程,进程无法捕获或忽略此信号。
如果发送信号为0,则没有任何信号发出,但是系统会进行错误检查,执行0来检验某个进程是否在执行。
成功执行会返回0,失败返回1
需要注意以下几点:
- 该进程有向指定进程发送信号的权限
- 系统进程不能接收信号,例如init进程
- 根用户可以向系统内任意进程发信号,因经根用户可以杀死任何恶意进程,同时得到根用户权限的恶意进程亦可以杀死系统内其它的进程
文件处理
打开文件 open
关闭文件 close
管道
建立管道 pipe
参考
int pipe(int filedes[2]);
在创建一个管道后,会获得一对文件描述符,用于读取和写入,然后将参数数组filedes中的两个值传递给获取到的两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向写端。
pipe()函数调用成功,返回值为0;否则返回-1
pipe()函数实现管道通信【图】
(1)在父进程中调用pipe()函数创建一个管道,产生一个文件描述符filedes[0]指向管道的读端和另一个文件描述符filedes[1]指向管道的写端。
(2)在父进程中调用fork()函数创建一个一模一样的新进程,也就是所谓的子进程。父进程的文件描述符一个指向读端,一个指向写端。子进程同理。
(3)在父进程关闭指向管道写端的文件描述符filedes[1],在子进程中,关闭指向管道读端的文件描述符filedes[0]。此时,就可以将子进程中的某个数据写入到管道,然后在父进程中,将此数据读出来。
发送信息write
int write(int fd, void *buf, size_t count);
buf种长度为count的消息写入文件描述符fd中
成功0,失败-1
接收信息read
int read(int fd, void *buf, size_t count);
从文件描述符fd中读取count字符给buf
成功0,失败-1
进程间互斥 lockf
int loackf(files,fonuction,size)
files 文件描述符
function 锁定和解锁模式
size 锁定解锁的字节数,0表示从当前位置到文件尾部
IPC系统调用
IPC 进程间通信
常用命令
文件操作命令:
-
显示文件内容:cat
cat filename1 filename2
按照指定顺序把文件名送到屏幕显示 -
复制文件:cp
cp source target
-
改名 mv
mv oldname newname
老名字改成新名字/移动到不同目录下 -
撤销文件命令 rm
-
确定文件类型命令:file 在屏幕上显示文件类型
目录操作命令
- 建立目录 mkdir(md)
- 撤销目录 rmdir(rd)
- 改变当前工作目录路径 cd
系统询问命令
- date 访问当前日期
- who 询问系统当前用户名
- pwd 显示当前目录路径名
重定向符
<输入转向 >输出转向
cat file1>file2
把file1内容输出到file2中(本来是在标准输出里)
wc 对标准输入中的行中字和字符进行计数
wc<file3
把file3中的内容作为wc的输入,即把文件从file3中读出的行中的字和字符进行计数
>>
输出转向符:用于把内容附加到文件末尾
a.out<file1>file0
a.out执行时从file1中提取数据,然后执行的结果输出到file0里
管道命令
符号|
链接两条命令,前一条命令的输出作为后一条命令的输入
cat file | wc
用命令cat把file中的内容取出并作为wc的输入
ps 报告进程的状态,类似于 windows 的任务管理器。
pstree 查看进程家族树
ps -ef | grep php 显示与php有关的进程
grep
若当中有我们所需要的信息,就将该行显示出来,该命令通常与管道命令一起使用,用于对一些命令的输出进行筛选加工等等
注意,重定向不会运行命令,管道命令才会运行命令