2018-10-25 20:41:38 qq_28869927 阅读数 1411

悉悉嗦嗦

对于接触计算机不久的同学来说,可能经常听到类似:Unix,Linux,类Unix,Mac OS X,BSD,FreeBSD,“Linux和Unix一样啦”,“差不多”等等名次和论断。

就我个人来说,本科阶段学习操作系统的时候,其实也没有认真去弄明白这其中的历史关系,有时候也是人云亦云,实际上也不懂它们之间到底有什么区别和联系。那时,仅仅是想学习Linux的使用、开发、运维等知识,对于Linux的发展历史和内核原理就当作是听听评书。

直到现在研究生阶段,在反反复复使用了cd, ls, chmod等等东西之后,恰逢学校开设了高级操作系统这门课,借着这个机会认认真真梳理一下Linux的发展史,以及其和Unix或者其他操作系统之间的区别和联系,也算是为自己答疑解惑了。

网上能直接找到的资料比较杂乱、鱼目混珠,在这里结合自己的专业知识进行了筛选和总结,希望能对大家有所帮助。

Unix的历史

Unix于1969年诞生于贝尔实验室,是Dennis Ritchie 和 Ken Thompson 两人灵感火花碰撞的产物。在此之前,贝尔实验室开发了一个多用户操作系统Multics。但很遗憾,这是一个失败的产物。正是由于Multics的失败,贝尔实验室发现处于没有操作系统可用的尴尬境地,于是乎设计了一个文件原型系统,也就演化成了后来的Unix系统。

1973年,Unix系统被利用C语言重写,这一行动也成为了后来Unix被广泛移植的最直接的原因。

这其中,一个比较著名的衍生开发版就是加州大学伯克利分校开发的Berkeley Software Distributions,即现在我们听到的BSD。最初的BSD修正了贝尔实验室Unix的一些bug,并且还集成了不少额外的软件。

基于BSD的开放特性,BSD以Darwin,FreeBSD,NetBSD和OpenBSD等形式继续发展。

在此基础上,许多厂商进行了定制性的开发,推出了他们自己的Unix系统,其中比较著名的就是Sun公司的SunOS和Solaris。

今天,Unix已经发展成了一个支持抢占式多任务、多线程、虚拟内存、换页、动态链接和TCP/IP网络的现代化操作系统,。

Linux简介

20世纪90年代初,为了满足Intel 80386微处理器的需求,Linus这位大神(没错,就是下面这位,图来源于百度百科)开发了一个全新的操作系统,即Linux系统。
在这里插入图片描述
所以,划重点:Linux 完全不等于 Unix,这是两个独立的操作系统。但是Linux和Unix完全没有关系吗?为什么使用起来感觉如此相似呢?

当时已经存在Unix系统和Microsoft 的 DOS系统。Linus热衷于Minix,一种教学的廉价Unix,但是这位大神终日为了不能修改Minix源码并发布这些修改而感到烦恼。同时期的DOS系统,对于Linus而言,除了玩游戏别无他用。

在这种情况下,Linus开始开发自己的操作系统。(嗯,那时这哥们儿还只是一名大学生)。于是,Linux诞生。由于其许可证条款的约定,Linux迅速成为多人合作的开发项目。

Linux被称之为类Unix系统,但不是Unix。这是因为Linux设计之初借鉴了Unix的许多规范化的设计思想并且实现了Unix的API(POSIX标准和其他Single Unix Specification定义),但是Linux并未直接使用Unix的源码,而是采用了和Unix一致的程序编程接口,这也就是为什么说二者是两个不同的操作系统。完全不能划等号=

Mac OS

关于Mac OS和Linux的发展及区别,可以参考:
【Linux && Mac OS】Mac OS 和 Linux 的内核有什么区别

2018-06-24 12:27:05 qq_36148847 阅读数 489

书柜上了本比较早买的一本书 《Unix/Linux系统管理技术手册》,所以最近拿来读一读并做下相关笔记和自我总结归纳

000

关于此书的第一章比较笼统地概括了一些入门的基本要素以及相关概念等。所以我用思维导图简单罗列了下

0001

对于书中提到的一些问题,我自认为自己不是很了解。所以也查询了相关资料,并得到了更深入的了解

Unix Vs Linux

0002

2019-07-29 09:59:57 qq_42685292 阅读数 1564

系统调用

UNIX/Linux系统绝大部分功能都是通过系统调用实现,比如:open/close…
UNIX/Linux把系统调用都封装成了C函数的形式,但他们并不是标准C的一部分。

一切皆文件

在UNIX/Linux系统下,几乎所有资源都是以文件形式提供了,所以在UNIX/Linux系统下一切皆文件,操作系统把它的服务、功能、设备抽象成简单的文件,提供一套简单统一的接口,这样程序就可以像访问磁盘上的文件一样访问串口、终端、打印机、网络等功能。
大多数情况下只需要 open/read/write/ioctl/close 就可以实现对各种设备的输入、输出、设置、控制等。 UNIX/Linux下几乎任何对象都可以当作特殊类型的文件,可以以文件的形式访问。

文件相关系统调用

open 打开或创建文件
creat 创建文件
close 关闭文件
read 读文件
write 写文件
lseek 设置文件读写位置
unlink 删除链接
remove 删除文件

(1) open()

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);

功能:打开文件

返回值:文件描述符

参数:

pathname:文件的路径
flags:打开的权限

O_RDONLY, 只读
O_WRONLY, 只写
O_RDWR,读写
O_NOCTTY, 当打开的是终端设备文件,不要把该文件当作主控终端。
O_TRUNC,清空
O_APPEND,追加

creat()

int creat (const char *pathname, int flags, mode_t mode);

功能:创建文件

pathname:文件的路径

flags:打开的权限

O_CREAT, 文件不存在则创建
O_EXCL,如果文件存在,则创建失败

mode:设置文件的权限

S_IRWXU 00700 read,write and execute permission
S_IRUSR00400 user has read permission
S_IWUSR 00200 user has writepermission
S_IXUSR 00100 user has execute permission
S_IRWXG 00070 read,write and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others have read, write and execute permission S_IROTH 00004 others have read permission
S_IWOTH 00002 others have writepermission
S_IXOTH 00001 others have execute permission

read()/write()

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

功能:从文件中读取数据到内存

fd:文件描述符,open函数的返回值

buf:数据的存储位置 count:读取的字节数

返回值:成功读取到的字节数

ssize_t write(int fd,const void *buf, size_t count);

功能:把数据写入到文件

fd:文件描述符,open函数的返回值

buf:要写入的数据内存首地址

count:要写入的字节数

返回值:成功写入的字节数

注意:如果把结构体以文本形式写入到文件,需要先把结构体转换成字符串。

2016-08-14 16:42:43 ShaoqunLiu 阅读数 844
  1. 文件系统安全:文件系统安全是Unix/Linux系统安全的核心,文件系统用来控制谁能访问信息以及他们能做什么(认证,授权,就像前面密码学里面提到的AAA技术,当然那个最后一个A还代表一个审计技术)
    1. 文件系统基础,在Unix/Linux系统中,所有的东西都是文件,这个说法早已听说过N次了吧。用户数据是文件,目录是文件,进程是文件,命令是文件,甚至网络连接也是文件
      Unix/Linux系统文件类型:

      1. 正规文件,这个名词起的比较恰当,就是普通的那些文件,比如ASCII文件,二进制数据文件,可执行文件等等
      2. 目录,目录是包含一组其他文件的二进制文件
  • 特殊文件,这个名词起的更恰当,通常就是在/dev文件下面的设备文件,对硬件驱动器进行信息编码
  1. 链接:硬链接,把两个或多个处于同一存储位置的文件名连接起来(像不像链表,结点是文件名,都处于同一个存储位置,就是内存)。软链接(符号链),就是指向其他文件
  2. Sockets,进程间通信使用的特殊文件
  3. 这些不同的文件类型以树的结构进行组织,以root作为树根,这个树中的所有内容统称文件系统,虽然现在很多的Unix系统都是基于许多磁盘和网络的文件系统组成,但是它只为用户提供一个树,树的每一项都由若干属性来描述,包括一个或多个名字、拥有者、分组、一组权限、长度和其他。这些属性被保存在了一个叫做i结点的数据结构中,一个文件系统中每一个i结点都有一个唯一的数字,每个文件有不同的i结点,i结点关注Unix系统所有的文件活动,Unix文件系统安全通过i结点中的3个信息来实现,第一个是UID指明了文件的拥有者,第二个是GID指明了文件所在的分组,第三个是模式,包括了文件的权限设置。当然i结点中还包含了文件大小,文件类型硬链接数目,文件修改时间等一系列文件的基本属性信息等。攻击者篡改文件时,往往修改i结点设置而留下一个足印
  1. 目录结构:还记得网站后台那个基于Debian Linux的目录结构吗,就拿那个来举一个例子
    1. /bin 用户命令的可执行文件,里面就是一大堆终端命令(比如mkdir创建目录)命名的文件,但是都是二进制形式,就好比system32一样
    2. /dev 特殊设备文件
  • /etc 系统执行文件,软件都装在这里吧,就好比windows里面的program file,比如你的ss服务端,是吧
  1. /home 就是用户的起始目录
  2. /lib 引导系统以及root文件系统中运行命令所需的共享库文件
  3. /lost+found 与特定的文件系统断开连接时丢失的文件
  • /mnt 临时安装的文件系统,比如CD-ROM和U盘,如果是U盘的话,需要自己使用命令行来挂载,怪哉之后会在mnt文件夹里面
  • /proc 一个伪文件系统,用来作为到内核数据结构或正在运行的进程的接口(多调试调用很有用)
  1. /sbin 为只被root使用的可执行文件以及那些只需要引导或安装/usr的文件保留
  2. /tmp 临时文件
  3. /usr 为用户和系统命令使用的可执行文件、头文件、共享库、帮助文件、本地程序。里面有一个名为include的文件夹,充满了C++的头文件,里面还有一个lib文件应该放的也是一些共享库文件,还有一个sbin文件
  • /var 里面存放一些数据,比如你的网站wordpress就是放在这里面的,还有一些统计文件和日志文件
  1. 文件权限,文件权限是Unix文件系统安全的关键,Unix文件系统中每一个文件属于一个用户和一个分组,通常一个文件属于创建它的用户和该用户的基本分组。每个文件和目录有3组权限与之相关,一组为文件的拥有者,一组为文件所属分组的成员,一组为其他用户(the world或others),每组权限由3个权限标志位来控制,就是那个rwx位,r可读,w可写,x可执行,刚刚说了一共有3组用户,那么这3组用户就有9个rwx位,再加上一位用来指明文件类型,这就是10个位,这被称为一个模式位,指明文件类型的那一位被放在最前面。放在最前面的文件类型可有如下几种表示方法,p为管道文件,d为目录文件,l为符号连接文件,-为普通文件,s为socket文件,c为字符设备文件,b为块设备文件。排序方式为:文件类型 文件拥有者权限 文件拥有组权限 其他人权限 链接数 所有者ID 所有组ID 文件大小 最后一次修改时间 文件名
    文件的权限还可以用八进制来表述
  2. SUID和SGID:SUID和SGID设置了用户ID和分组ID属性,允许用户以特殊的权限来运行程序,这种程序运行时执行具有文件所有者的权限和文件所在组的权限。如果一个文件被设置了SUID或SGID,会分别表现在所有者或同组用户的可执行位上,也就是RWX位中的X位,其中用s来代替,如果表现在第一个rwx(即文件所有者权限中),那么为SUID,如果设置在了第二个RWX位(所在组权限)中,那么为SGID,在UNIX的设置中,文件权限使用12个二进制位来控制,如果拥有此权限,那么设置为1,否则为0,这12个分别为SGTrwxrwxrwx,只说说SG,后面的前面提到了,S为11位,最后的那个x为0位,第一个S代表SUID,第二个G代表SGID 。SUID程序是为了使普通用户完成一些普通用户权限不能完成的位而设计的,比如用户都会被允许修改自己的密码,但是修改密码是需要root权限的,因此修改密码的程序都会以管理员的权限运行
    如果要查看这些文件的权限的话,可以使用ls -l来显示文件名和特性
  3. Unix文件权限的修改,在Unix系统中可以使用chmod命令来修改文件的权限,chmod语句只能由文件的拥有者或root来运行,chmod有两个变元,第一个是文件的权限,第二个是文件的名字,在权限变元的设计中可以使用绝对模式或者符号模式,绝对模式比如chmod 777 filename通过八进制的权限符来更改文件的权限
    当然,文件权限的修改也可以使用符号模式,符号模式中权限的修改就由一个八进制的数字变为了3个变元,变为了who op permission,其中who指的是用户的分组,其中u代表用户,g代表分组,其他使用0,全部使用a或者ugo。op选项使用+、-、=三个选项来控制,+指的是使得选择的权限加到已存在的文件权限中去,-指的是将其删除,=指的是文件只能拥有这些权限。permission选项使用rwx和SUID和SGID或者粘位n的任一组合。比如在一下的语句中chmod g+r filename 就是向文件filename中,对拥有者所在分组加入读权限。
    与chmod相关的还有chown(用于改变文件的所有权)和chgrp(用于改变所有者分组),命令的格式问chown user filename 出其中chown命令可以一次性改变拥有者和分组,对于拥有者和分组使用.来划分

  1. 创建文件的默认权限,创建一个文件的默认权限可以使用umask命令来设置,其中umask命令的用法为 umask 选项 权限值
首发于我的个人网站: 点击打开链接

2019-09-25 06:06:48 dog250 阅读数 1252

来自《Fork三部曲之clone的诞生》。本文fork三部曲的后传,建议先阅读:

在本文中,传统UNIX fork之后,我给出传统的UNIX fork在Linux内核中的变体clone系统调用的精彩。


若要理解fork的原始意义,还是要看Melvin Conway提出fork思想的原始论文:
A Multiprocessor System Design: https://archive.org/details/AMultiprocessorSystemDesignConway1963/page/n7
该论文的核心在于Conway分离了 “进程(process)”“处理器(processpr)” 的概念:

  • 一个进程不必特定于一个处理器上被处理。
  • 一个处理器未必处理特定的进程。
  • 系统中进程数量和处理器数量不需要相等。

fork为上述的核心思想提供了实现的手段。后来fork被引入到UNIX系统,成了创建新进程几十年不变的通用操作。

比较有意思的是,UNIX fork是通过著名的fork-exec序列而闻名于世的,而不是因为其提供的并行多处理手段而闻名于世,这可能是因为在线程概念出现以后,并行处理均由线程担当,也就在没有人记起fork了吧。

如果说一系列进程是 完全可并行 的,那么它们便没有资源是相互依赖的,这便是现代操作系统进程(即process)抽象的基础。可见,基于进程抽象的现代操作系统本身就是一个可并行系统。在一个可并行的系统中,进程之间本就是资源隔离的,如果需要join操作,引入IPC机制便是。

线程概念的出现,就是对UNIX进程抽象的资源如何共享重新解构再重构。

我们看看在线程出现之前,fork提供的并行多处理是多么高效。最典型的例子就是TCP服务编程模型了:

void handle_request(int csd)
{
	...
	// 读取请求
	ret = recv(csd, buf_req, len);
	ret = read(fd, buf_tosend, ret);
	ret = send(csd, buf_tosend, ret);
	close(csd);
}
void server(int sd)
{
	...
	while (1) {
		csd = accept(sd, 10);
		if (fork() == 0) {
			close(sd);
			handle_request(csd); // 可并行处理
		}
	}
}

这几乎成了服务器编程范式,是理解和设计select/poll/epoll程序的前提,也是理解后来Apache Web Server以及Nginx的基础。

以上这段简单代码,请问,用Windows的CreateProcess API如何实现?

不使用线程API,只用进程API,若要并行处理多个请求,CreateProcess需要载入一个磁盘程序映像来执行handle_request,该映像程序写出来可能是下面的样子(这不是最高效的写法,这只是一种直接的写法):

void handle_request(int csd)
{
	...
	// 读取请求
	ret = recv(csd, buf_req, len);
	ret = read(fd, buf_tosend, ret);
	ret = send(csd, buf_tosend, ret);
	close(csd);
}
int main(int argc, char **argv)
{
	char *client_info = argv[1];
	int sd;

	sd = GetOrCreateSocket(client_info);
	handle_request(sd);
}

我们知道载入一个程序的映像开销非常大,但为了并行处理不得不如此,否则Windows就必须串行处理handle_reques和接下来的accept。Windows没有fork,它没有可以实现进程在任意点的分叉的机制。

当然,现实中,Windows可以使用多线程API CreateThread来干这件事。还可以大肆声张多线程要比多进程方案高效。但如果没有多线程,想必Windows面对fork的挑衅只能忍气吞声而兴叹了。

因此,UNIX fork有两个层面的含义:

  1. 创建新进程,fork-exec序列(而不是fork本身)竞争Windows CreateProcess或者POSIX spawn。
  2. 并行多处理,fork作为多进程竞争多线程。

很明显,无论在哪个层面,fork均已落后于对手:

  1. 创建新进程,CreateProcess/spawn剔除了不必要的资源复制操作。
  2. 并行多处理,多线程共享资源替代了昂贵的IPC。

作为多进程的优化或者说替代,多线程的本质和fork的原始意义看起来并无太大的分歧。唯一的区别似乎就是资源共享的深度不同。

fork的原始意义将要在Linux内核task的设计中得到了延续和升华!

Linux内核的设计者似乎在很早以前就意识到了这一点,在很早的年代,Linux内核就没有去设计一个表示进程的结构体,而只设计了一个task_struct(以下简称task),该结构体包含有 让一个指令流能运行所需要的最少的东西! 因此它并不包含特定于进程或者线程概念的字段。

一个或者一组task对象到底是什么,关键看你怎么调配它! 就像使用相同的文字,组合不同,或是诅咒,或是祝福。

一个task对象只是一个原材料,它和其它task对象对资源的共享关系决定了它是什么。

是时候放出这张图了:
在这里插入图片描述
一组task对象按照下面的ID类型被标识为不同的实体:

enum pid_type
{
    PIDTYPE_PID,   
    PIDTYPE_TGID, 
    PIDTYPE_PGID,
    PIDTYPE_SID,
    PIDTYPE_MAX
};

关于上图更多的解释,参见下面的文章:
朴素的UNIX之-进程/线程模型: https://blog.csdn.net/dog250/article/details/40208219

对应底层关于task灵活的设计,必须给予应用程序调配它的接口以适应这种灵活。完成这种适配的是Linux的clone系统调用,该系统调用在很早的Linux内核(至少是2.2版本)中就已经存在了:

#define _GNU_SOURCE
#include <sched.h>

int clone(int (*fn)(void *), void *child_stack,
          int flags, void *arg, ...
          /* pid_t *ptid, void *newtls, pid_t *ctid */ );

/* For the prototype of the raw system call, see NOTES */

可见,参数众多,这里的flags参数就是让调用者控制如何和子进程共享资源的,拥有这种控制权是clone和fork最大的不同:
在这里插入图片描述
注意到clone函数的声明依赖于一个宏:

#define _GNU_SOURCE

这意味着clone是非标准的。确实,它只是Linux的一个系统调用。之所以存在这个灵活的clone调用,完全得益于Linux内核底层对task灵活的设计。

在传统UNIX系统或者类UNIX系统,未实现clone。这里面的原因可能是UNIX从一开始就明确定义了进程,到了后来,当UNIX不得不支持线程的时候,就要引入一个所谓 轻量级进程 的新概念,意思是可以共享某些资源的进程。参见名牌UNIX Solaris中lwp的实现。

在这些老牌Unix系统中,一开始过重的进程概念在引入多线程机制时造成了阻碍。然而对于Linux,为了支持线程引入新的数据结构完全没有必要。

虽然人们经常说clone调用创建的是轻量级进程,但也只是称呼罢了,Linux内核内部没有一个表示轻量级进程的结构体。

Linux内核在底层task设计以及系统调用接口如此这般的设计,注定它实现Posix线程规范是超级简单的。一个clone参数就能搞定:
在这里插入图片描述
注意后面那个 "(since Linux 2.4.0)" 注解,这意味着在2.4内核之前,Linux内核是不支持Posix线程的。但是这里说的不支持,只是无法在内核级实现Posix规范要求线程必须遵循的语义,并不是说在并行多处理机制上不支持,至于说POSIX线程的语义,在用户态支持也是一个办法,这都是2.4内核之前的事了。

2.4内核之后,Linux对线程的支持就完全是内核级的了。pthread库完全基于CLONE_THREAD实现。CLONE_THREAD的注释参见上图所示的clone manual。

具体如何创建一个线程呢?底层到底发生了什么呢?参见下面最简单demo:

#include <pthread.h>
#include <stdio.h>

void *func(void *unused)
{
	printf("sub thread\n");
	return (void *)123;
}

int main(int argc, char **argv)
{
	pthread_t t1;
	void *p;

	pthread_create(&t1, NULL, *func, NULL);
	pthread_join(t1, &p);
	printf("main thread:%d\n", (int)p);
	return 0;
}

关于线程,重要的有两点,即创建和销毁。让我们来strace一下:
在这里插入图片描述
其中,clone系统调用的flags参数的含义大致可以表述如下:

  • 黄色:指示都共享哪些资源,MM,FILES,FS等
  • 红色:实现POSIX线程的语义,比如共享进程PID,信号传递这些。

clone之后,就创建了一个线程。线程执行func之后便退出了。问题是,线程是如何退出的呢?

对于普通的C程序,我们知道main函数返回到了C库,而C库在main返回后会调用exit退出程序,而对于多线程程序,在编译代码的时候,我们显式链接了libpthread,那么类似C库的事情在多线程程序里就libpthread库代劳了。

大致的pthread_create应该是这个样子:

void clone_func(Thread *thread)
{
	ret = thread->fn(...);
	exit(ret);
}
int pthread_create(..., fn, ...)
{
	thread = malloc(sizeof(&thread));
	thread->fn = fn;
	ret = clone(clone_func, &thread);
	return ERR_NO(ret);
}

我们通过上面的strace可以看出,线程退出使用exit系统调用,而主进程退出则使用exit_group系统调用,二者的区别更多的是Posix进程/线程的语义上的,严格来讲,exit系统调用仅仅退出当前的task_struct,而exit_group则是退出当前task_struct所在进程的所有task_struct,对于多线程程序,它当然就是退出所有的线程了。

这就是Linux内核级线程的实现原理了。

但是,clone系统调用远不是仅仅实现多线程这么单一,它还可以优化UNIX fork的另一个层面。按照传统UNIX fork在两个层面的效用,Linux clone的对应描述如下:

  1. 在执行新进程层面,clone可以仅仅CLONE_VM实现轻量级进程快速exec以避免不必要的资源拷贝。
  2. 在并行多处理层面,如前所述,clone的CLONE_XX联合CLONE_THREAD可以实现内核级POSIX线程。

本文作为关于fork的后传,再不要说fork的不是了,fork的思想最终被Linux所继承和发扬,一切回归到了Conway在1963年的原始论文,并行多处理,终于在Linux clone系统调用上得到了落实:

  • clone可以创建多线程并行执行序列。
  • clone创建新进程,减少不必要的资源复制。

好了,这就是我要为你讲述的 “fork” 的故事的全部了。


浙江温州皮鞋湿,下雨进水不会胖。

UNIX/LINUX 哲学

阅读数 355

Unix/Linux哲学

阅读数 5

没有更多推荐了,返回首页