操作系统1实验复习

系统调用

程序接口就是系统调用
系统调用是通过中断机制实现的(通过软中断进入)
系统调用与一般调用的区别:

  1. 运行在不同系统状态。调用系统调用的程序在用户态,系统调用被调用的程序运行在内核态(系统态)
  2. 通过软中断进入。系统调用会涉及到系统状态的转换,因此需要执行访管指令(陷入指令)由软中断(陷入机制)转向对应的系统调用处理程序,同时CPU执行状态从用户态转换为系统态。
  3. 返回问题。通过中断返回指令退出,结束系统调用后,要分析原进程的优先级,如果有比它优先级更高的,则会将该进程放到就绪队列。
  4. 系统调用可以嵌套调用

特权指令:在系统态运行的指令。对内存空间的访问范围不受限制,可以访问用户空间和系统空间。只允许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
2
3
4
5
// execl("/usr/bin/ls","ls","-a","-l",NULL);
execlp("ls","ls","-a","-l",NULL); //等价上面的execl()
//虽然这里的第一个参数和第二个参数都一样,但是含义不一样;
//第一个参数表示iexeclp函数要执行命令的路径文件名,
//第二个参数表示execlp在命令行上如何执行该命令

如果调用的execl,那么第一个ls就要换成/user/bin/ls,之后的参数可以理解为传递一整个argv(argv[0]是程序名,之后是参数)

在使用exec函数族时,一定要加上错误判断语句。因为exec很容易执行失败,其中最常见的原因有:

  1. 找不到文件或路径,此时errno被设置为ENOENT。
  2. 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT。
  3. 没有对应可执行文件的运行权限,此时errno被设置为EACCES。

进程睡眠

头文件:#include <unistd.h>

定义函数:unsigned int sleep(unsigned int seconds);

函数说明:sleep()会令目前的进程暂停, 直到达到参数seconds 所指定的时间, 或是被信号所中断.

返回值:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。

结束进程

三种正常结束,两种异常终止
正常:

  1. main函数return=调用exit
  2. 调用exit函数
  3. 调用_exit函数
    异常终止:
  4. 调用abort
  5. 进程收到特定信号

执行命令行

system()
(用fork,exec和waitpid三个系统调用实现的)

暂停当前进程

pause()暂停运行直至收到了某个信号为止

信号处理

signal

系统调用signal是进程用来设定某个信号的处理方法,系统调用kill是用来发送信号给指定进程的。这 两个调用可以形成信号的基本操作。
signal为指定信号安装新的处理句柄。可以自定义16、17号信号
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:

  1. signum表示要处理的信号类型
  2. handler:描述与信号相关的动作,有三种情况
  • 一个处理函数,函数结束后,进程返回被中断的点继续执行
  • SIG_IGN:忽略参数signum所指的信号。
  • SIG_DFL:恢复参数signum所指信号的处理方法为默认值。

kill

系统调用kill用来向进程发送一个信号。该调用声明的格式如下:
int kill(pid_t pid, int sig);
参数:

  1. pid:
    • 大于 0 的整数:表示发送信号给具有该 PID 的单个进程。
    • 等于 0:表示发送信号给与调用进程属于同一进程组的所有进程。也就是调用kill函数的这个进程组的进程都会接受到这个信号。
    • 等于 -1:表示发送信号给除了调用进程和 init 进程(PID 为 1)以外的所有进程。通常情况下,这需要调用进程拥有特定权限,例如 root 用户权限。
    • 小于 -1 的整数:表示发送信号给进程组 ID 等于 pid 绝对值的所有进程。换句话说,如果 pid 是 -N(N > 1),则信号将发送给进程组 ID 为 N 的所有进程。
  2. sig:要发送的信号的编号
    sig 参数可以是整数信号代码,也可以是预定义的信号常量
    其中,SIGKILL(9):杀死信号,强制结束进程,进程无法捕获或忽略此信号。
    如果发送信号为0,则没有任何信号发出,但是系统会进行错误检查,执行0来检验某个进程是否在执行。

成功执行会返回0,失败返回1

需要注意以下几点:

  1. 该进程有向指定进程发送信号的权限
  2. 系统进程不能接收信号,例如init进程
  3. 根用户可以向系统内任意进程发信号,因经根用户可以杀死任何恶意进程,同时得到根用户权限的恶意进程亦可以杀死系统内其它的进程

文件处理

打开文件 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 进程间通信

常用命令

文件操作命令:

  1. 显示文件内容:cat cat filename1 filename2
    按照指定顺序把文件名送到屏幕显示

  2. 复制文件:cp cp source target

  3. 改名 mv mv oldname newname老名字改成新名字/移动到不同目录下

  4. 撤销文件命令 rm

  5. 确定文件类型命令:file 在屏幕上显示文件类型

目录操作命令

  1. 建立目录 mkdir(md)
  2. 撤销目录 rmdir(rd)
  3. 改变当前工作目录路径 cd

系统询问命令

  1. date 访问当前日期
  2. who 询问系统当前用户名
  3. pwd 显示当前目录路径名

重定向符
<输入转向 >输出转向
cat file1>file2
把file1内容输出到file2中(本来是在标准输出里)
wc 对标准输入中的行中字和字符进行计数
wc<file3 把file3中的内容作为wc的输入,即把文件从file3中读出的行中的字和字符进行计数
>>输出转向符:用于把内容附加到文件末尾
a.out<file1>file0a.out执行时从file1中提取数据,然后执行的结果输出到file0里

管道命令
符号|链接两条命令,前一条命令的输出作为后一条命令的输入
cat file | wc用命令cat把file中的内容取出并作为wc的输入

ps 报告进程的状态,类似于 windows 的任务管理器。
pstree 查看进程家族树
ps -ef | grep php 显示与php有关的进程
grep若当中有我们所需要的信息,就将该行显示出来,该命令通常与管道命令一起使用,用于对一些命令的输出进行筛选加工等等

注意,重定向不会运行命令,管道命令才会运行命令


操作系统1实验复习
https://mapllle.site/2023/10/16/StudySHUSHU/OS/OSexp/
作者
MAple
发布于
2023年10月16日
许可协议