8第八章Linux下的系统调用

合集下载

LinuxC讲解系统调用readdir,readdir_r以及如何遍历目录下的所有文件

LinuxC讲解系统调用readdir,readdir_r以及如何遍历目录下的所有文件

LinuxC讲解系统调⽤readdir,readdir_r以及如何遍历⽬录下的所有⽂件readdir与readdir_r简要说明readdir可以⽤来遍历指定⽬录路径下的所有⽂件。

不过,不包含⼦⽬录的⼦⽂件,如果要递归遍历,可以使⽤深度遍历,或者⼴度遍历算法。

readdir_r 是readdir的可重⼊版本,线程安全。

readdir因为直接返回了⼀个static的struct dirent,因此是⾮线程安全。

readdir如何遍历⽬录⼦⽂件?1. opendir打开⽬录opendir有2个版本:opendir,fopendir。

前者参数为⽬录对应字符串,后者参数为⽬录对应已打开⽂件描述符。

#include <sys/types.h>#include <dirent.h>DIR *opendir(const char *name);DIR *fdopendir(int fd);⽤法模型:DIR *dirp;const char *base_dir = "/home/martin/document";if ((dirp = opendir(base_dir)) != NULL) {perror("opendir error");return -1;}// 调⽤readdir遍历⽬录⼦⽂件...closedir(base_dir);2. readdir遍历⽬录⼦⽂件readdir需要⼀个已打开(调⽤opendir)的DIR对象作为参数。

#include <dirent.h>struct dirent *readdir(DIR *dirp);int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);dirent 结构定义struct dirent {ino_t d_ino; /* inode number i节点编号 */off_t d_off; /* not an offset; see NOTES 早期⽂件系统中,telldir返回⽂件在⽬录内的偏移 */unsigned short d_reclen; /* length of this record dirent 记录的实际长度 */unsigned char d_type; /* type of file; not supportedby all filesystem types ⽂件类型 */char d_name[256]; /* filename ⽂件名 */};成员介绍:d_ino i节点编号,操作系统⽤来识别⽂件的,每个⽂件都有⼀个inode number(参见)d_off 早期⽂件系统中,⽂件系统使⽤平⾯表格,telldir返回⽂件在⽬录内的偏移,⽽d_off就代表这个偏移的缓存。

操作系统第8章 操作系统实验

操作系统第8章 操作系统实验
理解Linux设备管理技术,学会Linux模块编程方法,掌握 基本的Linux设备驱动程序设计。
8.5.3 实验准备
1. Linux模块概述 2. 设备驱动程序的设计 3. 参考程序的分析
8.6 文件系统实验
8.6.1 实验内容
以root身份登录系统后,练习常用Linux文件操作命令以及 学习文件系统的装卸。
第8章 操作系统实验
内容提要
本教材以Linux操作系统为平台,通过它提供的键盘控制命令 了解操作系统的功能;通过它提供的系统调用命令实现进程 (线程)的同步与互斥、进程的通信、设备的管理等操作,从 而理解操作系统的工作原理。
本实验平台使用Red Hat Linux 9.0,并且使用文本操作界面。 实验内容包括Linux系统基本操作、进程通信、进程同步与互斥、 生产者与消费者、存储管理、设备管理、文件系统等实验。本 教材提供的实验同样适用于其他版本的Linux。
8.3 进程的同步与互斥实验
8.3.1 实验内容
1. 利用POSIX标准的pthread线程库创建五个线程,实现这 五个线程之间的互斥地访问数组N。这五个线程分别标识为0、 1、2、3、4,线程i的工作可描述如下: (1) 线程i休息一段时间,i可以是五个线程之一。 (2) 使N[i]加1,N[i]记录线程i进入临界区的次数。 (3) 使N[5]加1,记录这五个线程的进入临界区的总次数。 (4) 转(1)。 2. 利用POSIX标准的pthread线程库创建两个线程,实现这 两个线程之间的同步共享变量buffer(相当于一个缓冲区)。其 中一个线程产生一个随机数保存的变量buffer中,另一个线程将 该随机数打印出来。
第8章 操作系统实验
教学目标
通过本实验使学生理解操作系统的功能,掌握进程 (线程)的同步与互斥、进程的通信、设备的管理、文 件系统的实现原理,从而掌握操作系统的概念和原理。

什么是系统调用

什么是系统调用
什么是系统调用
xx年xx月xx日
目录
• 系统调用的定义和作用 • 系统调用的基本类别 • 系统调用的实现方式 • 系统调用的优缺点 • 系统调用技术的发展趋势
01
系统调用的定义和作用
什么是系统调用
系统调用是一种API,它允许应 用程序访问操作系统提供的核
心服务。
系统调用是操作系统提供给应 用程序的接口,用于实现操作
系统调用技术的应用前景
云计算
在云计算中,通过系统调用技术可以实现高效的资源管理 和调度。
物联网
在物联网中,系统调用技术可以用于实现各种设备的远程 管理和控制。
人工智能
人工智能需要大量的计算和存储资源,系统调用技术可以 用于实现高效的资源调度和管理。
安全领域
在安全领域,系统调用技术可以用于实现更加严格的安全 策略和防护机制,保障系统的安全性和可靠性。
系统调用可以实现获取系统时间、获取系统 负载、获取磁盘空间等操作,从而方便用户 对系统状态进行监控和管理。
系统调用可以实现启动和关闭外部设备、对 外部设备进行读写操作等操作,从而实现对 外部设备的控制和管理。
系统调用的基本原理
系统调用使用软件中断实现,应用程序通过系统调用请求操 作系统服务,操作系统通过中断处理程序将控制权转移到内 核,内核执行相应的服务后将结果返回给应用程序,应用程 序继续执行。
THANKS
谢谢您的观看
系统调用的接口
系统调用接口是操作系统提供给应用 程序使用的函数集合,用于向操作系 统请求服务。
系统调用接口通常包括文件操作、进 程控制、内存管理、网络通信等功能 的函数集合。
系统调用接口是操作系统提供的一种 标准化的服务,应用程序使用系统调 用接口来完成对系统资源的访问和管 理。

计算机操作系统第八章

计算机操作系统第八章

将每一组含有的盘块数和该组所有的盘块号,记入前一组的第一个盘块的S.free[0]~S.free[99]中。这样,各组的第一个盘块就链接成一个链表。
将第一组的盘块总数和所有的盘块号,记入空闲盘块号栈中,作为当前可供分配的空闲盘块号。
1
2
3
4
UNIX空闲盘块的组织
文件目录是一种数据结构,由若干目录项组成,每个目录项对应其中一个文件的FCB(包括文件名、文件体的物理地址、存取控制信息等),文件体另外存放。文件目录是用于检索文件的,一般的,目录项应包括以下内容:
整个系统只设一张文件目录表,集中存放文件存储器上所有文件的FCB,这是最简单的一种目录结构。目录表存于外存中的某块固定区域,系统初启或需要时调入内存,每个文件的FCB对应目录表中的一项,通过目录表就可以管理该系统中的所有文件,包括对文件的创建、检索和删除等。
两级文件目录
把登记文件的目录分成两级:主文件目录MFD,和用户文件目录UFD 。系统为每个用户各设置一个UFD,登记本用户所有文件的信息,每个UFD相当于一个一级目录;系统再设置一个MFD,用来登记所有用户的用户名及其UFD在外存上的物理地址、长度,物理结构等属性。
01
记录式文件,在逻辑上可看成是一组记录的集合。每个记录由彼此相关的若干个数据项组成。记录式文件中的逻辑记录可依次编号,其序号称为逻辑记录号(简称记录号)。
02
文件逻辑结构
按照文件的逻辑地址顺序存取。在记录式文件中,这种操作体现为按照记录的排列顺序来进行存取。
01
文件的随机存取 随机存取是指允许用户按照记录编号或者某一数据项的值随机存取存取任一记录。
二级索引存储结构
将索引表离散存储,即将索引表本身分为若干个逻辑块,存储在若干物理盘块中,将索引表所占的各盘块号记入另一个索引表——索引表的索引表。这种结构就称两级索引结构。

Linux内核中系统调用详解

Linux内核中系统调用详解

Linux内核中系统调用详解什么是系统调用?(Linux)内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。

用户可以通过系统调用命令在自己的应用程序中调用它们。

从某种角度来看,系统调用和普通的函数调用非常相似。

区别仅仅在于,系统调用由(操作系统)核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。

随Linux核心还提供了一些(C语言)函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。

为什么要用系统调用?实际上,很多已经被我们习以为常的C语言标准函数,在Linux 平台上的实现都是靠系统调用完成的,所以如果想对系统底层的原理作深入的了解,掌握各种系统调用是初步的要求。

进一步,若想成为一名Linux下(编程)高手,也就是我们常说的Hacker,其标志之一也是能对各种系统调用有透彻的了解。

即使除去上面的原因,在平常的编程中你也会发现,在很多情况下,系统调用是实现你的想法的简洁有效的途径,所以有可能的话应该尽量多掌握一些系统调用,这会对你的程序设计过程带来意想不到的帮助。

系统调用是怎么工作的?一般的,进程是不能访问内核的。

它不能访问内核所占内存空间也不能调用内核函数。

(CPU)(硬件)决定了这些(这就是为什么它被称作"保护模式")。

系统调用是这些规则的一个例外。

其原理是进程先用适当的值填充(寄存器),然后调用一个特殊的指令,这个指令会跳到一个事先定义的内核中的一个位置(当然,这个位置是用户进程可读但是不可写的)。

在(Intel)CPU中,这个由中断0x80实现。

硬件知道一旦你跳到这个位置,你就不是在限制模式下运行的用户,而是作为操作系统的内核--所以你就可以为所欲为。

进程可以跳转到的内核位置叫做sysem_call。

这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。

然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。

linux中系统调用中open函数读写权限mode具体参数

linux中系统调用中open函数读写权限mode具体参数

linux中系统调用中open函数读写权限mode具体参数
mode 的具体参数:
S_IRWXU
00700 允许文件的属主读 , 写和执行文件
S_IRUSR (S_IREAD)
00400允许文件的属主读文件
S_IWUSR (S_IWRITE)
00200允许文件的属主写文件
S_IXUSR (S_IEXEC)
00100允许文件的属主执行文件
S_IRWXG
00070允许文件所在的分组读 , 写和执行文件
S_IRGRP
00040允许文件所在的分组读文件
S_IWGRP
00020允许文件所在的分组写文件
S_IXGRP
00010允许文件所在的分组执行文件
S_IRWXO
00007允许其他用户读 , 写和执行文件
S_IROTH
00004允许其他用户读文件
S_IWOTH
00002允许其他用户写文件
S_IXOTH
00001允许其他用户执行文件
mode 只有当在 flags 中使用 O_CREAT 时才有效 , 否则被忽略.
creat 相当于open 的参数flags 等于
O_CREAT|O_WRONLY|O_TRUNC.。

Linux系统调用--getrlimit()与setrlimit()函数详解

Linux系统调用--getrlimit()与setrlimit()函数详解

功能描述:获取或设定资源使用限制。

每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。

非授权调用进程只可以将其软限制指定为0~硬限制范围中的某个值,同时能不可逆转地降低其硬限制。

授权进程可以任意改变其软硬限制。

RLI M_INFINITY的值表示不对资源限制。

用法:#include <sys/resource.h>int getrlimit(int resource, struct rlimit *rlim);int setrlimit(int resource, const struct rlimit *rlim);参数:resource:可能的选择有RLIMIT_AS//进程的最大虚内存空间,字节为单位。

RLIMIT_CORE//内核转存文件的最大长度。

RLIMIT_CPU//最大允许的CPU使用时间,秒为单位。

当进程达到软限制,内核将给其发送SIGXCPU信号,这一信号的默认行为是终止进程的执行。

然而,可以捕捉信号,处理句柄可将控制返回给主程序。

如果进程继续耗费CPU时间,核心会以每秒一次的频率给其发送SIGXCPU信号,直到达到硬限制,那时将给进程发送SIGKILL信号终止其执行。

RLIMIT_DATA//进程数据段的最大值。

RLIMIT_FSIZE//进程可建立的文件的最大长度。

如果进程试图超出这一限制时,核心会给其发送SIGXFSZ信号,默认情况下将终止进程的执行。

RLIMIT_LOCKS//进程可建立的锁和租赁的最大值。

RLIMIT_MEMLOCK//进程可锁定在内存中的最大数据量,字节为单位。

RLIMIT_MSGQUEUE//进程可为POSIX消息队列分配的最大字节数。

RLIMIT_NICE//进程可通过setpriority() 或nice()调用设置的最大完美值。

RLIMIT_NOFILE//指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。

linux、glibc中socket系统调用实现

linux、glibc中socket系统调用实现

/* %eax is < 0 if there was an error. */ cmpl $-125, %eax jae SYSCALL_ERROR_LABEL
/* Successful; return the syscall's value. */ L(pseudo_end):
ret ……
代码# define __socket socket 将__socket 定义为 socket,因此 ENTRY (__socket)即为 ENTRY (socket) 在这段汇编代码中,我们在 eax 保存当前系统调用号(这里是 socketcall),查看 SYS_ify 的定义,在 glibc/sysdeps/unix/sysv/linux/i386/sysdep.h 中:
#ifndef _SYS_SOCKETCALL_H #define _SYS_SOCKETCALL_H 1
/* Define unique numbers for the operations permitted on socket. Linux uses a single system call for all these functions. The relevant code file is /usr/include/linux/net.h. We cannot use a enum here because the values are used in assembler code. */
movl $SYS_ify(socketcall), %eax /* System call number in %eax. */
/* Use ## so `socket' is a separate token that might be #define'd. */
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

第八章 Linux下的系统调用8.1 系统调用介绍8.1.1 引言系统调用是内核提供的、功能十分强大的一系列函数。

它们在内核中实现,然后通过一定的方式(库、陷入等)呈现给用户,是用户程序与内核交互的一个接口。

如果没有系统调用,则不可能编写出十分强大的用户程序,因为失去了内核的支持。

由此可见系统调用的地位举足轻重。

内核的主体可以归结为:系统调用的集合;实现系统调用的算法。

8.1.2 系统调用的实现流程这里我们通过getuid()这个简单的系统调用来分析一下系统调用的实现流程。

在分析这个程序时并不考虑它的底层是如何实现的,而只需知道每一步执行的功能。

首先来看一个例子:#include <linux/unistd.h> /* all system call need this header*/int main(){int i=getuid();printf(“Hello World! This is my uid: %d\n”,i);}#include<linux/unistd.h>是每个系统调用都必须要的头文件,当系统执行到getuid()时,根据unistd.h中的宏定义把getuid()展开。

展开后程序把系统调用号__NR_getuid(24)放入eax,然后通过执行“int $0x80”这条指令进行模式切换,进入内核。

int 0x80指令由于是一条软中断指令,所以就要看系统规定的这条中断指令的处理程序是什么。

arch/i386/kernel/traps.cset_system_gate(SYSCALL_VECTOR,&system_call);从这行程序我们可以看出,系统规定的系统调用的处理程序就是system_call。

控制转移到内核之前,硬件会自动进行模式和堆栈的切换。

现在控制转移到了system_call,保留系统调用号的最初拷贝之后,由SAVE_ALL来保存上下文,得到该进程结构的指针,放在ebx里面,然后检查系统调用号,如果__NR_getuid(24)是合法的,则根据这个系统调用号,索引sys_call_table,得到相应的内核处理程序:sys_getuid。

执行完sys_getuid之后,保存返回值,从eax移到堆栈中的eax处,假设没有意外发生,于是ret_from_sys_call直接到RESTORE_ALL恢复上下文,从堆栈中弹出保存的寄存器,堆栈切换,iret。

执行完iret后,正如我们所分析的,进程回到用户态,返回值保存在eax中,于是得到返回值,打印:Hello World! This is my uid: 551这时这个最简单的调用系统调用的程序到这里就结束了,系统调用的流程也理了一遍。

跟系统调用相关的内核代码文件主要有:arch/i386/kernel/entry.Sinclude/linux/unistd.h下面将分别介绍这两个文件。

8.1.3 entry.S文件相关说明这个文件中包含了系统调用和异常的底层处理程序,信号量程序。

一.关于SAVE_ALL,RESTORE_ALLarch/i386/kernel/entry.S#define SAVE_ALLcld;pushl %es;pushl %ds;pushl %eax;pushl %ebp;pushl %edi;pushl %esi;pushl %edx;pushl %ecx;pushl %ebx;movl $(__KERNEL_DS),%edx;movl %edx,%ds;movl %edx,%es;#define RESTORE_ALLpopl %ebx;popl %ecx;popl %edx;popl %esi;popl %edi;popl %ebp;popl %eax;1: popl %ds;2: popl %es;addl $4,%esp;3: iret;这一部分程序主要执行的任务就是中断时保存进程的上下文以及执行中断后恢复进程上下文的环境。

二.系统调用表(sys_call_table)在这个文件中还有一个重要的地方就是维护整个系统调用的一张表――系统调用表。

系统调用表一次保存着所有系统调用的函数指针,以方便总的系统调用处理程序(system_call)进行索引调用。

arch/i386/kernel/entry.SENTRY(sys_call_table).long SYMBOL_NAME(sys_ni_syscall).long SYMBOL_NAME(sys_exit).long SYMBOL_NAME(sys_fork).long SYMBOL_NAME(sys_read)….long SYMBOL_NAME(sys_ni_syscall).long SYMBOL_NAME(sys_ni_syscall)1 .rept NR_syscalls-(.-sys_call_table)/4.long SYMBOL_NAME(sys_ni_syscall).endr1行中的‘.’代表当前地址,sys_call_table代表数组首地址,所以1行中两个变量相减,得到差值表示这个系统调用表的大小(两个地址之间相差的byte数),除以4,得到现在的系统调用个数。

用NR_syscalls 减去系统调用个数,得到的是没有定义的系统调用。

然后用.rept ….long SYMBOL_NAME(sys_ni_syscall).endr往数组的剩余空间里填充sys_ni_syscall。

三.system_call和ret_from_sys_callarch/i386/kernel/entry.SENTRY(system_call)pushl %eax # save orig_eaxSAVE_ALLGET_CURRENT(%ebx)testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYSjne tracesyscmpl $(NR_syscalls),%eaxjae badsyscall *SYMBOL_NAME(sys_call_table)(,%eax,4)movl %eax,EAX(%esp) # save the return value ENTRY(ret_from_sys_call)cli # need_resched and signals atomic testcmpl $0,need_resched(%ebx)jne reschedulecmpl $0,sigpending(%ebx)jne signal_returnrestore_all:RESTORE_ALL这部分代码所完成的任务主要如下:首先,系统把eax(里面存放着系统调用号)的值压入堆栈,就是把原来的eax值保存起来,因为使用SAVE_ALL保存起来的eax要用来保存返回值。

但是在保存了返回值到真正返回到用户态还有一些事情要做,内核可能还会需要知道哪个系统调用导致进程陷入了内核。

所以,这里要保留一份eax的最初拷贝。

保存进程上下文。

取得当前进程的task_struct结构的指针返回到ebx中。

看看进程是不是被监视了,如果是则跳转到tracesys检查eax中的参数是否合法。

调用具体的系统调用代码,然后保存返回值到堆栈中。

系统调用返回。

恢复进程上下文。

最后我们用类c代码简化一下system_call过程:void system_call(unsigned int eax){task struct *ebx;save_context();ebx=GET_CURRENT;if(ebx->tsk_ptrace!=0x02)goto tracesys;if(eax>NR_syscalls)goto badsys;retval=(sys_call_table[eax*4])();if(ebx->need_resched!=0)goto reschedule;if(ebx->sigpending!=0)goto signal_return;restore_context();}8.1.4 系统调用中参数的传递及unistd.h前面讲的都是内核中的处理。

进行系统调用的时候可能是这样:getuid()。

那么内核是怎么样跟用户程序进行交互的呢?这包括控制权是怎样转移到内核中的那个system_call处理函数去的,参数是如何传递的等等。

在这里标准C库充当了很重要的角色,它是把用户希望传递的参数装载到CPU的寄存器中,然后发出0x80中断。

当从系统调用返回的时候(ret_from_sys_call)时,标准C库又接过控制权,处理返回值。

头文件include/asm-i386/unistd.h定义了所有的系统调用号,还定义了几个与系统调用相关的关键的宏。

include/asm-i386/unistd.h#define __NR_exit 1#define __NR_fork 2#define __NR_read 3#define __NR_write 4……#define __NR_exit_group 252#define __NR_lookup_dcookie 253#define __NR_set_tid_address 258很清楚,文件一开始就定义了所有的系统调用号,每一个系统调用号都以“__NR_”开头,这可以说是一种习惯,或者说是一种约定。

但事实上,它还有更方便的地方,那就是除了这个“__NR_”头外,所有的系统调用号就是你编写用户程序的那个名字。

标准库函数正是通过这样的共同性,通过宏替换,把一个个用户程序调用的诸如getuid这样的名词转换为__NR_getuid,然后再转换成相应的数字号,通过eax寄存器传递给内核作为深入syscall_table的索引。

接下来,文件连续定义了7个宏,很多系统调用都是通过这些宏,进行展开形成定义,这样用户程序才能进行系统调用。

内核也才能知道用户具体的系统调用,然后进行具体的处理。

使用这些宏把系统调用的工作基本上都是标准C库来做的,所以标准C库是用户程序和内核之间的一个桥梁。

我们可以挑选一个带3个参数的宏来看,include/asm-i386/unistd.h#define_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) type name(type1 arg1,type2 arg2,type3 arg3){long __res;__asm__ volatile ("int $0x80": "=a" (__res): "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long) (arg2)),"d" ((long)(arg3)));__syscall_return(type,__res);}这个宏用于展开不用参数的系统调用,比如open(“/tmp/foo”,O_RDONLY,S_IREAD)。

相关文档
最新文档