首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

【Linux教程】Linux系统零基础编程入门,想当大神?这些你都要学

?文件和文件系统

? ? ?文件是Linux系统中最重要的抽象,大多数情况下你可以把linux系统中的任何东西都理解为文件,很多的交互操作其实都是通过文件的读写来实现的。

文件描述符

? ? ?在Linux内核中,文件是用一个整数来表示的,称为?文件描述符,通俗的来说,你可以理解它是文件的id(唯一标识符)

?普通文件

??普通文件就是字节流组织的数据。

??文件并不是通过和文件名关联来实现的,而是通过关联索引节点来实现的,文件节点拥有文件系统为普通文件分配的唯一整数值(ino),并且存放着一些文件的相关元数据。

?目录与链接

? 正常情况下文件是通过文件名来打开的。

????目录是可读名称到索引编号之间的映射,名称和索引节点之间的配对称为链接。

??可以把目录看做普通文件,只是它包含着文件名称到索引节点的映射(链接)

?进程

? ? ?进程是仅次于文件的抽象概念,简单的理解,进程就是正在执行的目标代码,活动的,正在运行的程序。不过在复杂情况下,进程还会包含着各种各样的数据,资源,状态甚至虚拟计算机。

你可以这么理解进程:它是竞争计算机资源的基本单位。

?进程、程序与线程

????程序

? ? ?程序,简单的来说就是存在磁盘上的二进制文件,是可以内核所执行的代码

????进程

? ? ?当一个用户启动一个程序,将会在内存中开启一块空间,这就创造了一个进程,一个进程包含一个独一无二的PID,和执行者的权限属性参数,以及程序所需代码与相关的资料。

? ? ?进程是系统分配资源的基本单位。

? ? ?一个进程可以衍生出其他的子进程,子进程的相关权限将会沿用父进程的相关权限。

????线程

? ? ?每个进程包含一个或多个线程,线程是进程内的活动单元,是负责执行代码和管理进程运行状态的抽象。

? ? ?线程是独立运行和调度的基本单位。

?进程的层次结构(父进程与子进程)

? ? ?在进程执行的过程中可能会衍生出其他的进程,称之为子进程,子进程拥有一个指明其父进程PID的PPID。子进程可以继承父进程的环境变量和权限参数。

? ? ?于是,linux系统中就诞生了进程的层次结构——进程树。

? ? ?进程树的根是第一个进程(init进程)。

??过程调用的流程: fork & exec

? ? ?一个进程生成子进程的过程是,系统首先复制(fork)一份父进程,生成一个暂存进程,这个暂存进程和父进程的区别是pid不一样,而且拥有一个ppid,这时候系统再去执行(exec)这个暂存进程,让他加载实际要运行的程序,最终成为一个子进程的存在。

??进程的结束

? ? ?当一个进程终止时,并不会立即从系统中删除,内核将在内存中保存该进程的部分内容,允许父进程查询其状态(这个被称为等待终止进程)。

? ? ?当父进程确定子进程已经终止,该子进程将会被彻底删除。

? ? ?但是如果一个子进程已经终止,但父进程却不知道它的状态,这个进程将会成为?僵尸进程

?服务与进程

简单的说服务(daemon)就是常驻内存的进程,通常服务会在开机时通过init.d中的一段脚本被启动。

?进程通信

? ? ?进程通信的几种基本方式:管道,信号量,消息队列,共享内存,快速用户控件互斥。

?程序,进程和线程

? ? ?现在我们再次详细的讨论这三个概念

??程序(program)

? ? ?程序是指编译过的、可执行的二进制代码,保存在储存介质上,不运行。

??进程(process)

? ? ?进程是指正在运行的程序。

? ? ?进程包括了很多资源,拥有自己独立的内存空间。

??线程

? ? ?线程是进程内的活动单元。

? ? ?包括自己的虚拟储存器,如栈、进程状态如寄存器,以及指令指针。

在单线程的进程中,线程即进程。而在多线程的进程中,多个线程将会共享同一个内存地址空间

运行一个进程

? ? ?创建一个进程,在unix系统中被分为了两个流程。

● 把程序载入内存并执行程序映像的操作:exec

● 创建一个新进程:fork

?exec

??? 最简单的exec系统调用函数:execl()

● 函数原型:

int execl(const char * path,const chr * arg,...)

execl()调用将会把path所指的路径的映像载入内存,替换当前进程的映像。

参数arg是以第一个参数,参数内容是可变的,但最后必须以NULL结尾。

●?举例:

int ret;

ret = execl("/bin/vi","vi",NULL);

if (ret == -1) {

? perror("execl");

}

? ? ?上面的代码将会通过/bin/vi替换当前运行的程序

? ? ?注意这里的第一个参数vi,是unix系统的默认惯例,当创建、执行进程时,shell会把路径中的最后部分放入新进程的第一个参数,这样可以使得进程解析出二进制映像文件的名字。

int ret;

ret = execl("/bin/vi","vi","/home/mark/a.txt",NULL);

if (ret == -1) {

? perror("execl");

}

? ? ?上面的代码是一个非常有代表性的操作,这相当于你在终端执行以下命令:

vi /home/mark/a.txt

??????●?返回值:

? ? ?正常情况下其实execl()不会返回,调用成功后会跳转到新的程序入口点。

? ? ?成功的execl()调用,将改变地址空间和进程映像,还改变了很多进程的其他属性。

? ? ?不过进程的PID,PPID,优先级等参数将会被保留下来,甚至会保留下所打开的文件描述符(这就意味着它可以访问所有这些原本进程打开的文件)。

? ? ?失败后将会返回-1,并更新errno。

●?其他exec系函数

? ? ?略,使用时查找

?fork

通过fork()系统调用,可以创建一个和当前进程映像一模一样的子进程。

●?函数原型

pid_t fork(void)

调用成功后,会创建一个新的进程(子进程),这两个进程都会继续运行。

●?返回值

如果调用成功,

父进程中,fork()会返回子进程的pid,在子进程中返回0;

如果失败,返回-1,并更新errno,不会创建子进程。

??●?举例

我们看下面这段代码

#include

#include

int main ()

{

? pid_t fpid; //fpid表示fork函数返回的值

? int count=0;

? printf("this is a process\n");

? fpid=fork();

? if (fpid < 0)

? ? ? printf("error in fork!");

? else if (fpid == 0) {

? ? ? printf("i am the child process, my process id is %d\n",getpid());

? ? ? printf("我是爹的儿子\n");

? ? ? count++;

? }

? else {

? ? ? printf("i am the parent process, my process id is %d\n",getpid());

? ? ? printf("我是孩子他爹\n");

? ? ? count++;

? }

? printf("统计结果是: %d\n",count);

? return 0;

}

这段代码的运行结果比较神奇,是这样的:

this is a process

i am the parent process, my process id is 21448

我是孩子他爹

统计结果是: 1

i am the child process, my process id is 21449

我是爹的儿子

统计结果是: 1

? ? ?在执行了fork()之后,这个程序就拥有了两个进程,父进程和子进程分别往下继续执行代码,进入了不同的if分支。

? ? ?如何理解pid在父子进程中不同?

? ? ?其实就相当于链表,进程形成了链表,父进程的pid指向了子进程的pid,因为子进程没有子进程,所以pid为0。

??????●?写时复制

? ? ?传统的fork机制是,调用fork时,内核会复制所有的内部数据结构,复制进程的页表项,然后把父进程的地址空间按页复制给子进程(非常耗时)。

? ? ?现代的fork机制采用了一种惰性算法的优化策略。

? ? ?为了避免复制时系统开销,就尽可能的减少“复制”操作,当多个进程需要读取他们自己那部分资源的副本时,并不复制多个副本出来,而是为每个进程设定一个文件指针,让它们读取同一个实际文件。

? ? ?显然这样的方式会在写入时产生冲突(类似并发),于是当某个进程想要修改自己的那个副本时,再去复制该资源,(只有写入时才复制,所以叫写时复制)这样就减少了复制的频率。

?联合实例

? ? ?在程序中创建一个子进程,打开另一个应用。

pid_t pid;

pid = fork();

if (pid == -1)

? perror("fork");

//子进程

if (!pid) {

? const char * args[] = {"windlass",NULL};

? int ret;

? // 参数以数组方式传入

? ret = execv("/bin/windlass",args);

? if (ret == -1) {

? ? ? perror("execv");

? ? ? exit(EXIT_FAILURE);

? }

}

? ? ?上面的程序创建了一个子进程,并且使子进程运行了/bin/windlas程序。

?终止进程?exit()

??????●?函数原型

void exit (int status)

? ? ?该函数用于终止当前的进程,参数status只用于标识进程的退出状态,这个值将会被传送给当前进程的父进程用于判断。

? ? ?还有一些其他的终止调用函数,在此不赘述。

如果你也很想学编程,可以来我的C语言/C++编程学习基地!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200820A0H29A00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com