系统调用

操作系统为在用户态运行的进程与硬件设备进行交互提供了一组插口。这说明了系统调用的插口是操作系统提供的。

系统调用与libc库的关系

unix系统给程序员提供了好多API的库函数。libc的标准C库所定义的一些API引用了封装解释器(wrapper

routine)(其惟一目的就是发布系统调用)。一般情况下,每位系统调用对应一个封装解释器,而封装类库定义了应用程序使用的API【是不是说,有些系统调用没有使用封装解释器?】。

反之则不然,一个API没必要对应一个特定的系统调用。首先,API可以直接提供用户态的服务(比如一些具象的物理函数,根本没必要使用系统调用)。其次,一个单独的API可能调用了几个系统调用。据悉,几个API函数可能调用封装不同功能的同一系统调用。

POSIX标准

POSIX标准针对API而不是针对系统调用。判定一个系统是否与posix兼容要看它是否提供一组合适的应用程序插口,而不管对应的函数是怎样实现的。

系统调用处理程序及服务解释器

linux内核系统调用_linux内核调用shell_linux系统内核分为几块

当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。

在80x86体系结构中,可以使用两种不同的方法调用Linux的系统调用。两种形式的最终结果都是跳转到所谓系统调用处理程序(systemcallhandler)的汇编语言函数。

系统调用处理程序的处理过程?

步入和退出系统调用

本地应用可以通过两种不同的方法调用系统调用:

同样,内核可以通过两种方法从系统调用退出,进而使CPU切换到用户态:

参数传递

与普通函数类似,系统调用一般也须要输入输出参数,这种参数而且是实际的值(比如数值)linux操作系统安装,也可能是用户态进程地址空间的变量linux内核系统调用,甚至是指向用户态函数的表针的数据地址地址结构。

fork()系统调用并不须要其他的参数。

linux系统内核分为几块_linux内核调用shell_linux内核系统调用

普通的C函数的参数传递是通过把参数值写入活动的程序栈(用户态栈或则内核态栈)实现的。由于系统调用时一种横越用户和内核两台湾地的特殊函数,所以既不能使用用户态栈也不能使用内核态栈。更准确地说,在发出系统调用之前,系统调用的参数被写入到CPU寄存器中,之后在调用系统调用服务类库(系统调用的实际处理程序)之前,内核再把储存在CPU中的参数拷贝到内核态堆栈中,这是由于系统调用服务类库是普通的C函数。

为何内核不直接把参数从用户态栈拷贝到内核态的栈呢?首先,同时操作两个栈是比较复杂的。其次,寄存器的使用促使系统调用处理程序的结构和其他异常处理程序的结构类似。

验证参数

在内核准备满足用户的恳求之前,必须仔细地检测所有的系统调用参数。检测的类型既依赖于系统调用,也依赖于特定的参数。只要一个参数指定的是地址,内核必须检测它是否在这个进程的地址空间之内。有两种可能的方法来执行这些检测:

验证线性地址大于PAGE_OFFSET是判定它的有效性的必要条件而不是充分条件。

为何要进行这些简略检测?

访问进程地址空间

系统调用服务解释器须要十分频繁地读写进程地址空间的数据。Linux包含的一组宏是这些访问愈发容易。get_user()宏从一个地址读取1、2或4个连续字节;put_user()宏把这几种大小的内容写入一个地址中。

了解几个内核访问进程地址空间的函数和宏,以便阅读和理解内核源码。

linux系统内核分为几块_linux内核调用shell_linux内核系统调用

动态地址检测:修正代码

里面检测系统调用参数指定的地址是否大于PAGE_OFFSET只能保证用户态进程不会企图进犯内核地址空间。并且,由参数传递的线性地址仍然可能不属于进程地址空间。在这些情况下,当内核企图使用任何此类错误地址时,将会发生缺页异常。

在描述内核怎样测量这些错误之前linux内核系统调用,我们先说明一下在内核态造成缺页异常的中学情况。这种情况必须由缺页异常处理程序来分辨,由于不怜悯况采取的操作很大不同:

1.内核企图访问属于进程地址空间的页,然而,相应的页框不存在或则是内核企图去写一个只读页。在这种情况下,处理程序必须分配和初始化一个新的页框。【缺页异常程序–在80x86上就是do_page_fault()】

2.内核轮询到属于其地址的页linux系统怎么样,并且相应的页表项还没有初始化。在这些情况下,内核必须在当前进程页表中适当的完善一些表项。

3.某一内核函数包含编程错误,当这个函数运行时就造成异常(就是我们常说的内核bug);或则可能是因为顿时的硬件错误导致异常。当这些情况发生时,处理程序必须执行一个内核漏洞。

4.本章所讨论的一种情况:当系统调用服务解释器企图读写一个显存区,而该显存区的地址是通过系统调用参数传递来的,但却不属于进程的地址空间。

内核封装类库

内核封装类库对应于libc库中的封装类库。虽然系统调用主要是有用户态进程使用,而且也可以被内核线程使用,而且内核线程不是使用libc库函数。

为了简化相应封装解释器的申明,Linux定义了7个从_syscall0到_syscall6的一组宏。每位宏名子中的数字0~6对应着系统调用所用的参数个数(系统调用号除外)。但是,不能用这种宏来为超过6个参数(系统调用号除外)的系统调用或形成非标准返回值的系统调用来定义封装类库。

==================================================================================

系统调用号

unistd.h是 C 和 C++ 程序设计语言中提供对 POSIX 操作系统 API 的访问功能的头文件的名称。
linux-3.18.132includeuapiasm-genericunistd.h

/* ipc/mqueue.c */
#define __NR_mq_open 180
__SC_COMP(__NR_mq_open, sys_mq_open, compat_sys_mq_open)
#define __NR_mq_unlink 181
__SYSCALL(__NR_mq_unlink, sys_mq_unlink)
#define __NR_mq_timedsend 182
__SC_COMP(__NR_mq_timedsend, sys_mq_timedsend, compat_sys_mq_timedsend)
#define __NR_mq_timedreceive 183
__SC_COMP(__NR_mq_timedreceive, sys_mq_timedreceive, 
	  compat_sys_mq_timedreceive)
#define __NR_mq_notify 184
__SC_COMP(__NR_mq_notify, sys_mq_notify, compat_sys_mq_notify)
#define __NR_mq_getsetattr 185
__SC_COMP(__NR_mq_getsetattr, sys_mq_getsetattr, compat_sys_mq_getsetattr)
/* ipc/msg.c */
#define __NR_msgget 186
__SYSCALL(__NR_msgget, sys_msgget)
#define __NR_msgctl 187
__SC_COMP(__NR_msgctl, sys_msgctl, compat_sys_msgctl)
#define __NR_msgrcv 188
__SC_COMP(__NR_msgrcv, sys_msgrcv, compat_sys_msgrcv)
#define __NR_msgsnd 189
__SC_COMP(__NR_msgsnd, sys_msgsnd, compat_sys_msgsnd)
/* ipc/sem.c */
#define __NR_semget 190
__SYSCALL(__NR_semget, sys_semget)
#define __NR_semctl 191
__SC_COMP(__NR_semctl, sys_semctl, compat_sys_semctl)
#define __NR_semtimedop 192
__SC_COMP(__NR_semtimedop, sys_semtimedop, compat_sys_semtimedop)
#define __NR_semop 193
__SYSCALL(__NR_semop, sys_semop)
/* ipc/shm.c */
#define __NR_shmget 194
__SYSCALL(__NR_shmget, sys_shmget)
#define __NR_shmctl 195
__SC_COMP(__NR_shmctl, sys_shmctl, compat_sys_shmctl)
#define __NR_shmat 196
__SC_COMP(__NR_shmat, sys_shmat, compat_sys_shmat)
#define __NR_shmdt 197
__SYSCALL(__NR_shmdt, sys_shmdt)

系统调用处理程序

COMPAT_SYSCALL_DEFINE4

SYSCALL_DEFINE4

/*path--ipc/compat_mq.c*/
COMPAT_SYSCALL_DEFINE4(mq_open, const char __user *, u_name,
		       int, oflag, compat_mode_t, mode,
		       struct compat_mq_attr __user *, u_attr)
{
	void __user *p = NULL;
	if (u_attr && oflag & O_CREAT) {
		struct mq_attr attr;
		memset(&attr, 0, sizeof(attr));
		p = compat_alloc_user_space(sizeof(attr));
		if (get_compat_mq_attr(&attr, u_attr) ||
		    copy_to_user(p, &attr, sizeof(attr)))
			return -EFAULT;
	}
	return sys_mq_open(u_name, oflag, mode, p);
}
/*path--ipc/mqueue.c*/
SYSCALL_DEFINE4(mq_open, const char __user *, u_name, int, oflag, umode_t, mode,
		struct mq_attr __user *, u_attr)
{
	struct path path;
	struct file *filp;
	struct filename *name;
	struct mq_attr attr;
	int fd, error;
	struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns;
	struct vfsmount *mnt = ipc_ns->mq_mnt;
	struct dentry *root = mnt->mnt_root;
	int ro;
	if (u_attr && copy_from_user(&attr, u_attr, sizeof(struct mq_attr)))
		return -EFAULT;
	audit_mq_open(oflag, mode, u_attr ? &attr : NULL);
	if (IS_ERR(name = getname(u_name)))
		return PTR_ERR(name);
	fd = get_unused_fd_flags(O_CLOEXEC);
	if (fd d_inode->i_mutex);
	path.dentry = lookup_one_len(name->name, root, strlen(name->name));
	if (IS_ERR(path.dentry)) {
		error = PTR_ERR(path.dentry);
		goto out_putfd;
	}
	path.mnt = mntget(mnt);
	if (oflag & O_CREAT) {
		if (path.dentry->d_inode) {	/* entry already exists */
			audit_inode(name, path.dentry, 0);
			if (oflag & O_EXCL) {
				error = -EEXIST;
				goto out;
			}
			filp = do_open(&path, oflag);
		} else {
			if (ro) {
				error = ro;
				goto out;
			}
			audit_inode_parent_hidden(name, root);
			filp = do_create(ipc_ns, root->d_inode,
						&path, oflag, mode,
						u_attr ? &attr : NULL);
		}
	} else {
		if (!path.dentry->d_inode) {
			error = -ENOENT;
			goto out;
		}
		audit_inode(name, path.dentry, 0);
		filp = do_open(&path, oflag);
	}
	if (!IS_ERR(filp))
		fd_install(fd, filp);
	else
		error = PTR_ERR(filp);
out:
	path_put(&path);
out_putfd:
	if (error) {
		put_unused_fd(fd);
		fd = error;
	}
	mutex_unlock(&root->d_inode->i_mutex);
	if (!ro)
		mnt_drop_write(mnt);
out_putname:
	putname(name);
	return fd;
}

Linux内核错误码位置

linux-3.18.132includeuapiasm-genericerrno-base.h
linux-3.18.132includeuapiasm-genericerrno.h

glibc库源码

__NR_mq_open

#ifdef __NR_mq_open
/* Establish connection between a process and a message queue NAME and
   return message queue descriptor or (mqd_t) -1 on error.  OFLAG determines
   the type of access used.  If O_CREAT is on OFLAG, the third argument is
   taken as a `mode_t', the mode of the created message queue, and the fourth
   argument is taken as `struct mq_attr *', pointer to message queue
   attributes.  If the fourth argument is NULL, default attributes are
   used.  */
mqd_t
__mq_open (const char *name, int oflag, ...)
{
  if (name[0] != '/')
    {
      __set_errno (EINVAL);
      return -1;
    }
  mode_t mode = 0;
  struct mq_attr *attr = NULL;
  if (oflag & O_CREAT)
    {
      va_list ap;
      va_start (ap, oflag);
      mode = va_arg (ap, mode_t);
      attr = va_arg (ap, struct mq_attr *);
      va_end (ap);
    }
  return INLINE_SYSCALL (mq_open, 4, name + 1, oflag, mode, attr);
}
strong_alias (__mq_open, mq_open);
#else
#include 
#endif

系统调用处理程序及服务类库

xyz()系统调用对应的服务类库的名子一般是sys_xyz()。不过也有一些例外。

步入和退出系统调用总结参考文献及源码《深入理解LINUX内核》陈莉君张琼声张宏伟译

本文原创地址:https://www.linuxprobe.com/xtdyylkdgx.html编辑:刘遄,审核员:暂无