8第八章Linux下的系统调用
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.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核心还提供了一些(C语言)函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。
为什么要用系统调用?实际上,很多已经被我们习以为常的C语言标准函数,在Linux 平台上的实现都是靠系统调用完成的,所以如果想对系统底层的原理作深入的了解,掌握各种系统调用是初步的要求。
进一步,若想成为一名Linux下(编程)高手,也就是我们常说的Hacker,其标志之一也是能对各种系统调用有透彻的了解。
即使除去上面的原因,在平常的编程中你也会发现,在很多情况下,系统调用是实现你的想法的简洁有效的途径,所以有可能的话应该尽量多掌握一些系统调用,这会对你的程序设计过程带来意想不到的帮助。
系统调用是怎么工作的?一般的,进程是不能访问内核的。
它不能访问内核所占内存空间也不能调用内核函数。
(CPU)(硬件)决定了这些(这就是为什么它被称作"保护模式")。
系统调用是这些规则的一个例外。
其原理是进程先用适当的值填充(寄存器),然后调用一个特殊的指令,这个指令会跳到一个事先定义的内核中的一个位置(当然,这个位置是用户进程可读但是不可写的)。
在(Intel)CPU中,这个由中断0x80实现。
硬件知道一旦你跳到这个位置,你就不是在限制模式下运行的用户,而是作为操作系统的内核--所以你就可以为所欲为。
进程可以跳转到的内核位置叫做sysem_call。
这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。
然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。
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()函数详解
功能描述:获取或设定资源使用限制。
每种资源都有相关的软硬限制,软限制是内核强加给相应资源的限制值,硬限制是软限制的最大值。
非授权调用进程只可以将其软限制指定为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系统调用实现
/* %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. */
linux命令——strace命令(跟踪进程中的系统调用)
linux命令——strace命令(跟踪进程中的系统调⽤)strace常⽤来跟踪进程执⾏时的系统调⽤和所接收的信号。
在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(⽐如读取磁盘⽂件,接收⽹络数据等等)时,必须由⽤户态模式切换⾄内核态模式,通过系统调⽤访问硬件设备。
strace可以跟踪到⼀个进程产⽣的系统调⽤,包括参数,返回值,执⾏消耗的时间。
1、参数每⼀⾏都是⼀条系统调⽤,等号左边是系统调⽤的函数名及其参数,右边是该调⽤的返回值。
strace 显⽰这些调⽤的参数并返回符号形式的值。
strace 从内核接收信息,⽽且不需要以任何特殊的⽅式来构建内核。
$strace cat /dev/nullexecve("/bin/cat", ["cat", "/dev/null"], [/* 22 vars */]) = 0brk(0) = 0xab1000access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f29379a7000access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)...参数含义-c 统计每⼀系统调⽤的所执⾏的时间,次数和出错的次数等.-d 输出strace关于标准错误的调试信息.-f 跟踪由fork调⽤所产⽣的⼦进程.-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.-F 尝试跟踪vfork调⽤.在-f时,vfork不被跟踪.-h 输出简要的帮助信息.-i 输出系统调⽤的⼊⼝指针.-q 禁⽌输出关于脱离的消息.-r 打印出相对时间关于,,每⼀个系统调⽤.-t 在输出中的每⼀⾏前加上时间信息.-tt 在输出中的每⼀⾏前加上时间信息,微秒级.-ttt 微秒级输出,以秒了表⽰时间.-T 显⽰每⼀调⽤所耗的时间.-v 输出所有的系统调⽤.⼀些调⽤关于环境变量,状态,输⼊输出等调⽤由于使⽤频繁,默认不输出.-V 输出strace的版本信息.-x 以⼗六进制形式输出⾮标准字符串-xx 所有字符串以⼗六进制形式输出.-a column设置返回值的输出位置.默认为40.-e expr指定⼀个表达式,⽤来控制如何跟踪.格式如下:[qualifier=][!]value1[,value2]...qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之⼀.value是⽤来限定的符号或数字.默认的 qualifier是 trace.感叹号是否定符号.例如:-eopen等价于 -e trace=open,表⽰只跟踪open调⽤.⽽-etrace!=open表⽰跟踪除了open以外的其他调⽤.有两个特殊的符号 all 和 none.注意有些shell使⽤!来执⾏历史记录⾥的命令,所以要使⽤\\.-e trace=set只跟踪指定的系统调⽤.例如:-e trace=open,close,rean,write表⽰只跟踪这四个系统调⽤.默认的为set=all.-e trace=file只跟踪有关⽂件操作的系统调⽤.-e trace=process只跟踪有关进程控制的系统调⽤.-e trace=network跟踪与⽹络有关的所有系统调⽤.-e strace=signal跟踪所有与系统信号有关的系统调⽤-e trace=ipc跟踪所有与进程通讯有关的系统调⽤-e abbrev=set设定 strace输出的系统调⽤的结果集.-v 等与 abbrev=none.默认为abbrev=all.-e raw=set将指定的系统调⽤的参数以⼗六进制显⽰.-e signal=set指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表⽰不跟踪SIGIO信号.-e read=set输出从指定⽂件中读出的数据.例如:-e read=3,5-e write=set输出写⼊到指定⽂件中的数据.-o filename将strace的输出写⼊⽂件filename-p pid跟踪指定的进程pid.-s strsize指定输出的字符串的最⼤长度.默认为32.⽂件名⼀直全部输出.-u username以username 的UID和GID执⾏被跟踪的命令2、使⽤实例实例1:跟踪可执⾏程序strace -f -F -o ~/straceout.txt myserver-f -F选项告诉strace同时跟踪fork和vfork出来的进程,-o选项把所有strace输出写到~/straceout.txt⾥⾯,myserver是要启动和调试的程序。
Fedora Linux 系统调用或常用命令详细解析 rmmod
COPYRIGHT
This manual page Copyright 2002, Rusty Russell, IBM Corporation.
SEE ALSO
modprobe(8), insmod(8), lsmod(8)
2002-12D(8)
NAME
rmmod − simple program to remove a module from the Linux Kernel
SYNOPSIS
rmmod [ -f ] [ -w ] [ -s ] [ -v ] [ modulename ]
DESCRIPTION
rmmod is a trivial program to remove a module from the kernel. Most users will want to use modprobe(8) with the -r option instead.
OPTIONS
-v --verbose Print messages about what the program is doing. Usually rmmod prints messages only if something goes wrong. -f --force This option can be extremely dangerous: it has no effect unless CONFIG_MODULE_FORCE_UNLOAD was set when the kernel was compiled. With this option, you can remove modules which are being used, or which are not designed to be removed, or have been marked as unsafe (see lsmod(8)). -w --wait Normally, rmmod will refuse to unload modules which are in use. With this option, rmmod will isolate the module, and wait until the module is no longer used. Nothing new will be able to use the module, but it’s up to you to make sure the current users eventually finish with it. See lsmod(8)) for information on usage counts. -s --syslog Send errors to syslog instead of standard error. -V --version Show version of program and exit.
系统调用和库函数
系统调用和库函数一、系统调用系统调用是操作系统提供给应用程序的接口,它允许应用程序请求操作系统执行某些特权操作,例如读写文件、创建进程、打开网络连接等。
在Linux系统中,系统调用是通过软中断来实现的。
1.1 系统调用的分类Linux系统中有很多种类型的系统调用,按照功能可以分为以下几类:1. 进程控制类:如fork()、exec()等;2. 文件操作类:如open()、read()、write()等;3. 设备操作类:如ioctl()、mmap()等;4. 网络通信类:如socket()、connect()等;5. 内存管理类:如mmap()、brk()等。
1.2 系统调用的使用方法在C语言中,可以使用unistd.h头文件中定义的函数来进行系统调用。
例如:#include <unistd.h>int main(){char buf[1024];int fd = open("test.txt", O_RDONLY);read(fd, buf, sizeof(buf));close(fd);return 0;}上面的代码就是使用了open()和read()两个系统调用来读取一个文本文件。
二、库函数库函数是一组预先编写好的函数集合,可以被应用程序直接调用。
库函数通常被编译成动态链接库或静态链接库,以便于应用程序使用。
在Linux系统中,常见的库函数有标准C库函数、数学库函数、字符串处理库函数等。
2.1 标准C库函数标准C库函数是C语言提供的一组基本的函数,包括输入输出、字符串处理、内存管理等方面。
在Linux系统中,标准C库通常是glibc。
下面是一些常用的标准C库函数:1. 输入输出类:printf()、scanf()、fopen()、fclose()等;2. 字符串处理类:strcpy()、strcat()、strlen()等;3. 内存管理类:malloc()、calloc()、realloc()等。
linux操作系统原理
linux操作系统原理Linux操作系统是一种开源的、多用户、多任务的操作系统,基于Unix的设计理念和技术,由芬兰的林纳斯·托瓦兹(Linus Torvalds)在1991年首次发布。
其原理主要包括以下几个方面:1. 内核与外壳:Linux操作系统的核心是Linux内核,负责管理计算机的资源并为用户程序提供服务。
外壳(Shell)则是用户与内核之间的接口,提供命令行或图形用户界面供用户操作系统。
2. 多用户和多任务:Linux支持多用户和多任务,可以同时运行多个用户程序,并为每个用户分配资源。
多任务由调度器负责,按照一定的算法将CPU时间片分配给各个任务,以提高系统的利用率。
3. 文件系统:Linux采用统一的文件系统作为数据的存储与管理方式。
文件系统将计算机中的存储设备抽象成为一个层次化的文件和目录结构,使用户可以方便地访问和管理文件。
4. 设备管理:Linux操作系统通过设备驱动程序管理计算机的外部设备,如键盘、鼠标、打印机等。
每个设备都有相应的驱动程序,将硬件操作转换成可供内核或用户程序调用的接口。
5. 系统调用:Linux操作系统提供了一组系统调用接口,允许用户程序通过调用这些接口来访问内核提供的功能。
常见的系统调用包括文件操作、进程管理、内存管理等,通过系统调用可以使用户程序与操作系统进行交互。
6. 网络支持:Linux操作系统具有强大的网络功能,支持网络协议栈和网络设备驱动程序。
Linux可以作为服务器提供各种网络服务,如Web服务器、数据库服务器等。
7. 安全性:Linux操作系统注重安全性,提供了许多安全机制来保护系统和数据。
例如,文件权限控制、访问控制列表、加密文件系统等可以保护文件的机密性和完整性;防火墙和入侵检测系统可以保护网络安全。
总之,Linux操作系统具有高度的可定制性、稳定性和安全性,适用于服务器、嵌入式设备和个人计算机等各种场景。
在开源社区的支持下,Linux不断发展壮大,成为当今最受欢迎的操作系统之一。
【转载】Linux系统调用SYSCALL_DEFINE详解
【转载】Linux系统调⽤SYSCALL_DEFINE详解系统调⽤在内核中的⼊⼝都是sys_xxx,但其实Linux的系统调⽤都改为SYSCALL_DEFINE定义的。
本⽂以socket系统调⽤为例来详解。
1 ⾸先看⼀下SYSCALL_DEFINE的定义,如下:1 #define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void)2 #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)3 #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)4 #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)5 #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)6 #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)7 #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)2 宏SYSCALL_DEFINEx的定义:1 #define SYSCALL_DEFINEx(x, name, ...) \2 asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \3 static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \4 asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \5 { \6 __SC_TEST##x(__VA_ARGS__); \7 return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \8 } \9 SYSCALL_ALIAS(sys##name, SyS##name); \10 static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))3 下⾯以socket系统调⽤为实例来分析,其定义:1 SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)2 {3 int retval;4 struct socket *sock;5 int flags;67 /* Check the SOCK_* constants for consistency. */8 BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);9 BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);10 BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);11 BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);1213 flags = type & ~SOCK_TYPE_MASK;14 if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))15 return -EINVAL;16 type &= SOCK_TYPE_MASK;1718 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))19 flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;2021 retval = sock_create(family, type, protocol, &sock);22 if (retval < 0)23 goto out;2425 retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));26 if (retval < 0)27 goto out_release;2829 out:30 /* It may be already another descriptor 8) Not kernel problem. */31 return retval;3233 out_release:34 sock_release(sock);35 return retval;36 }3.1 ##和__VA_ARGS__其中##是连接符,__VA_ARGS__代表前⾯...⾥⾯的可变参数。
第八章-嵌入式系统设计与应用—基于ARM...和Linux(第2版)-王剑-清华大学出版社
struct resource * __request_region(struct resource *, resource_size_t start, resource_size_t n, const char *name, int
这两种方式在硬件实现上的差异对软件来说是完全可见的。
2. 并发控制 在驱动程序中经常会出现多个进程同时访问相同的 资源时可能会出现竞态(race condition),即竞争资 源状态,因此必须对共享资料进行并发控制。Linux 内核中解决并发控制最常用的方法是自旋锁 (spinlocks)和信号量(semaphores)。
1. 内存与 I/O 端口 编写驱动程序大多数情况下其本质都是对内存和 I/O 端口的操 作。 (1) 内存
Linux通常有以下几种地址 类型: 用户虚拟地址 物理地址 总线地址 内核逻辑地址 内核虚拟地址
(2)I/O 端口
有两个重要的内核调用可以保证驱动程序使用正确的端口,它 们定义在 include/linux/ioport.h 中。
8.1.1 Linux 设备驱动程序分类
1. 字符设备 字符设备是传输数据以字符为单位进行的设备,字符设备驱动 程序通常实现open、close、read和write等系统调用函数,常见 的字符设备有键盘、串口、控制台等。通过文件系统节点可以 访问字符设备,例如/dev/tty1和/dev/lp1。字符设备和普通文件 系统之间唯一的区别是普通文件允许往复读写,而大多数字符 设备驱动仅是数据通道,只能顺序读写。此外,字符设备驱动 程序不需要缓冲且不以固定大小进行操作,它与用户进程之间 直接相互传输数据。
系统调用的实现原理【转】
系统调⽤的实现原理【转】在看《unix/linux编程实践教程》时,忽然意识到,系统调⽤是如何实现的?在实际编程中,往往是调⽤相关的函数,⽐如open(),read()等等。
但是调⽤这些函数怎么可能让程序的运⾏在⽤户空间和内核空间切换呢?看了下⾯的⽂章,才知道怎么回事。
让我想到了《计算机组成原理》中讲到的东西。
原⽂地址:系统调⽤1什么是系统调⽤系统调⽤,顾名思义,说的是操作系统提供给⽤户程序调⽤的⼀组“特殊”接⼝。
⽤户程序可以通过这组“特殊”接⼝来获得操作系统内核提供的服务,⽐如⽤户可以通过⽂件系统相关的调⽤请求系统打开⽂件、关闭⽂件或读写⽂件,可以通过时钟相关的系统调⽤获得系统时间或设置定时器等。
从逻辑上来说,系统调⽤可被看成是⼀个内核与⽤户空间程序交互的接⼝——它好⽐⼀个中间⼈,把⽤户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给⽤户空间。
系统服务之所以需要通过系统调⽤来提供给⽤户空间的根本原因是为了对系统进⾏“保护”,因为我们知道Linux的运⾏空间分为内核空间与⽤户空间,它们各⾃运⾏在不同的级别中,逻辑上相互隔离。
所以⽤户进程在通常情况下不允许访问内核数据,也⽆法使⽤内核函数,它们只能在⽤户空间操作⽤户数据,调⽤⽤户空间函数。
⽐如我们熟悉的“hello world”程序(执⾏时)就是标准的⽤户空间进程,它使⽤的打印函数printf就属于⽤户空间函数,打印的字符“hello word”字符串也属于⽤户空间数据。
但是很多情况下,⽤户进程需要获得系统服务(调⽤系统程序),这时就必须利⽤系统提供给⽤户的“特殊接⼝”——系统调⽤了,它的特殊性主要在于规定了⽤户进程进⼊内核的具体位置;换句话说,⽤户访问内核的路径是事先规定好的,只能从规定位置进⼊内核,⽽不准许肆意跳⼊内核。
有了这样的陷⼊内核的统⼀访问路径限制才能保证内核安全⽆虞。
我们可以形象地描述这种机制:作为⼀个游客,你可以买票要求进⼊野⽣动物园,但你必须⽼⽼实实地坐在观光车上,按照规定的路线观光游览。
第8讲 系统调用
system_call: … sys_xyz() … ret_from_sys_ call: … iret 系统调用 处理程序
sys_xy z(){ … }
在应用程序 调用中的 系统调用
在libc标准库 中的封装例程
系统调用 服务例程
<
>
系统调用执行过程
1、程序调用libc库的封装函数 2、调用软中断 int 0x80 进入内核。 3、在内核中首先执行system_call函数,接着根 据系统调用号在系统调用表中查找到对应的系统 调用服务例程 4、执行该服务例程 5、执行完毕后,转入ret_from_sys_call例程, 从系统调用返回
内核,待内核把请求处理完毕后再将处理
结果送回给用户空间。
< >
系统调用-内核的出口
优点:
使编程更加容易; 提高了系统的安全性;
提高了程序的可移植性。
< >
系统调用与API
区别: (1)应用编程接口(API) 是一组函数定义,这些函数说明了如何获 得一个给定的服务;而系统调用是通过软中断向内核发出一个明确的 请求; (2)系统调用的实现是在内核完成的,而API函数是在函数库中实现 的。 联系:
系统调用与系统命令
• 联系:
(1)系统命令相对应用编程接口更高一层,每个
系统命令都是一个可执行程序,比如ls、
hostname等;
(2)系统命令的实现调用了系统调用,可通过 “strace 命令名”查看 。
< >
系统调用与内核函数
内核函数定义:内核函数在形式上与普通函数一 程的要求 。 联系:
(1)不能引用c库函数 (2)缺少内存保护措施 样,但它是在内核实现的,需要满足一些内核编 (3)嵌套不能过深(堆栈有限)
系统调用
xlanchen@2007.6.19
Embedded Operating Systems
6
系统调用处理程序也其他异常处理程序的结构 类似,执行下列操作
在进程的内核态堆栈中保存大多数寄存器的内容 (即保存恢复进程到用户态执行所需要的上下文) 调用名为系统调用服务例程的相应的C函数来处理 系统调用 通过ret_from_sys_call()从系统调用返回
xlanchen@2007.6.19
Embedded Operating Systems
7
应用程序、封装例程、 应用程序、封装例程、系统调用处理程序及系统调用 服务例程之间的关系
xlanchen@2007.6.19 Embedded Operating Systems 4
系统调用程序及服务例程
当用户态进程调用一个系统调用时,CPU切换 到内核态并开始执行一个内核函数。
在Linux中是通过执行int $0x80这条汇编语言来执 行系统调用的,这条汇编指令产生向量为128的编 程异常
xlanchen@2007.6.19 Embedded Operating Systems 14
SAVE_ALL
Sys_write需要的参数
xlanchen@2007.6.19
Embedded Operating Systems
15
传递返回值
服务例程的返回值是将会被写入eax寄存器中 这个是在执行“return”指令时,由编译器自动完 成的
传参: 内核实现了很多不同的系统调用,进程必须传 递一个名为系统调用号的参数来指明需要调用 的系统调用,eax寄存器就用作这个目的
Linux系统调用
Linux系统调⽤所谓系统调⽤是指操作系统提供给⽤户程序调⽤的⼀组“特殊”接⼝,⽤户程序可以通过这组“特殊”接⼝来获得操作系统内核提供的服务。
例如⽤户可以通过进程控制相关的系统调⽤来创建进程、实现进程调度、进程管理等。
在这⾥,为什么⽤户程序不能直接访问系统内核提供的服务呢?这是由于在 Linux 中,为了更好地保护内核空间,将程序的运⾏空间分为内核空间和⽤户空间(也就是常称的内核态和⽤户态),它们分别运⾏在不同的级别上,在逻辑上是相互隔离的。
因此,⽤户进程在通常情况下不允许访问内核数据,也⽆法使⽤内核函数,它们只能在⽤户空间操作⽤户数据,调⽤⽤户空间的函数。
但是,在有些情况下,⽤户空间的进程需要获得⼀定的系统服务(调⽤内核空间程序),这时操作系统就必须利⽤系统提供给⽤户的“特殊接⼝”——系统调⽤规定⽤户进程进⼊内核空间的具体位置。
进⾏系统调⽤时,程序运⾏空间需要从⽤户空间进⼊内核空间,处理完后再返回到⽤户空间。
Linux 系统调⽤部分是⾮常精简的系统调⽤(只有 250 个左右),它继承了 UNIX 系统调⽤中最基本和最有⽤的部分。
这些系统调⽤按照功能逻辑⼤致可分为进程控制、进程间通信、⽂件系统控制、系统控制、存储管理、⽹络管理、socket 控制、⽤户管理等⼏类。
在 Linux 中对⽬录和设备的操作都等同于⽂件的操作,因此,⼤⼤简化了系统对不同设备的处理,提⾼了效率。
Linux 中的⽂件主要分为 4种:普通⽂件、⽬录⽂件、链接⽂件和设备⽂件。
那么,内核如何区分和引⽤特定的⽂件呢?这⾥⽤到的就是⼀个重要的概念——⽂件描述符。
对于 Linux ⽽⾔,所有对设备和⽂件的操作都使⽤⽂件描述符来进⾏的。
⽂件描述符是⼀个⾮负的整数,它是⼀个索引值,并指向内核中每个进程打开⽂件的记录表。
当打开⼀个现存⽂件或创建⼀个新⽂件时,内核就向进程返回⼀个⽂件描述符;当需要读写⽂件时,也需要把⽂件描述符作为参数传递给相应的函数。
linux系统调用 完全剖析 x86
用户程序需要系统提供服务的时候,会通过系统调用产生一个int 0x80的软中断,就会进入到系统调用的入口函数,入口函数存放在以下文件当中:以下是系统调用的入口:ENTRY(system_call)RING0_INT_FRAME # cant unwind into user space anywaypushl %eax # save orig_eax ,将系统调用号压入栈中CFI_ADJUST_CFA_OFFSET 4SAVE_ALL #将寄存器的值压入堆栈当中,压入堆栈的顺序对应着结构体struct pt_regs ,当出栈的时候,就将这些值传递到结构体struct pt_regs里面的成员,从而实现从汇编代码向C程序传递参数。
Struct pt_regs 对应定义在struct pt_regs {long ebx;long ecx;long edx;long esi;long edi;long ebp;long eax;int xds;int xes;int xfs;int xgs;long orig_eax;long eip;int xcs;long eflags;long esp;int xss;};#GET_THREAD_INFO宏获得当前进程的thread_info结构的地址,获取当前进程的信息。
GET_THREAD_INFO(%ebp)# system call tracing in operation / emulation#thread_inof结构中flag字段的_TIF_SYSCALL_TRACE或_TIF_SYSCALL_AUDIT#被置1。
如果发生被跟踪的情况则转向相应的处理命令处。
testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)jnz syscall_trace_entry #比较结果不为零的时候跳转。
#对用户态进程传递过来的系统调用号的合法性进行检查。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 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)。