Linux应用程序设计_05-进程

进程概念

  1. 进程是一个独立的可调度的任务 进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源
  2. 进程是一个程序的一次执行的过程
  3. 进程是程序执行和资源管理的最小单位

进程和程序的区别

  1. 程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念
  2. 进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡

Linux下的进程结构

主要的进程标识

进程号(Process Identity Number,PID)
父进程号(Parent Process ID,PPID)

PID唯一地标识一个进程
Linux中的进程包含三个段

“数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。)
“正文段”存放的是程序中的代码
“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量

进程与程序

Linux应用程序设计_05-进程_第1张图片

进程不仅包括程序的指令和数据,而且包括程序计数器值、CPU的所有寄存器值以及存储临时数据的进程堆栈。

Linux系统中的进程类型

  1. 交互进程:交互式进程:这类进程经常与用户进行交互,需要等待用户的输入(键盘和鼠标操作等)。当接收到用户的输入之后,这类进程能够立刻响应。典型的交互式进程有 shell 命令进程、文本编辑器和图形应用程序 运行等。

  2. 批处理进程:这类进程不必与用户进行交互,因此通常在后台运行。因为这类进程通常不必很快地响应,因此往往不会优先调度。典型的批处理进程是编译器的编译操作、数据库搜索引擎等

  3. 守护进程:这类进程一直在后台运行,和任何终端都不关联。通常系统启动时开始执行,系统关闭时才结束。很多系统进程(各种服务)都是以守护进程的形式存在

进程运行状态

  1. 运行态(R):此时进程或者正在运行,或者准备运行。
  2. 等待态:此时进程在等待一个事件的发生或某种系统资源。 可中断(S) 不可中断(D)
  3. 停止态(T):此时进程被中止。
  4. 死亡态(X):这是一个已终止的进程,但还在进程向量数组中占有一个task_struct结构。
  5. 僵尸态(Z):这是一种特殊的状态,子进程退出时,父进程未回收子进程的资源

进程状态图
Linux应用程序设计_05-进程_第2张图片

查看进程信息

调度进程
ps 查看系统中的进程
top 动态显示系统中的进程
nice 按用户指定的优先级运行进程
renice 改变正在运行进程的优先级
kill 结束进程(包括后台进程)
bg 将挂起的进程在后台执行
fg 把后台运行的进程放到前台运行

ps 查看系统进程
Linux应用程序设计_05-进程_第3张图片
Linux应用程序设计_05-进程_第4张图片
top 查看进程动态信息
Linux应用程序设计_05-进程_第5张图片
/proc 查看进程详细信息

Linux应用程序设计_05-进程_第6张图片
nice 按用户指定的优先级运行进程

linux@linux:~/Desktop/Homework/L1_LinuxC$ nice -n 5 ./test 

在这里插入图片描述
renice 改变正在运行进程的优先级
在这里插入图片描述
jobs 查看后台进程
在这里插入图片描述
bg 将挂起的进程在后台运行
在这里插入图片描述
fg 把后台运行的进程放到前台运行
在这里插入图片描述

进程创建 – fork

fork函数实际上是完成一个子进程的创建,子进程是对
父进程几乎完整的复制,或者说子进程继承了父进程的
相关属性;

子进程从父进程处继承了整个进程的地址空间,包括进
程上下文、代码段、进程堆栈、内存信息、打开的文件
描述符、信号处理函数、进程优先级、进程组号、当前
工作目录、根目录、资源限制和控制终端等,而子进程
所独有的只有它的进程号、资源使用和计时器等

Linux应用程序设计_05-进程_第7张图片
备注 :该函数执行成功之后,将会产生一个新的子进程,在新的子进程中其返回值为 0,在原来的父进程中其返回值为大于 0 的正整数,该正整数就是子进程的 PID

Linux应用程序设计_05-进程_第8张图片

int main(int argc, const char *argv[])
{
	pid_t fpid; //fpid表示fork函数返回的值
	int count = 0;
	
	
	fpid = fork();

	if(fpid < 0)
	{
		puts(" Error in fork!");

	}
	else if(fpid == 0)/*返回值为0代表子进程*/
	{
		printf(" \n I am child process, my process id is %d\n",getpid());
		count++;
	}
	else /*返回值大于0代表父进程*/
	{
		printf(" \n I am parents process, my process id is %d\n",getpid());
		count++;
	}
	
	printf(" sum = %d\n ",count);

	return 0;
}

运行结果:
在这里插入图片描述
在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.

fork出错可能有两种原因:
1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
2)系统内存不足,这时errno的值被设置为ENOMEM。

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
fork执行完毕后,出现两个进程,
Linux应用程序设计_05-进程_第9张图片
有人说两个进程的内容完全一样啊,怎么打印的结果不一样啊,那是因为判断条件的原因,上面列举的只是进程的代码和指令,还有变量啊。
执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。
还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。

父子进程

子进程继承了父进程的内容
父子进程有独立的地址空间,互不影响
若父进程先结束:
子进程成为孤儿进程,被init进程收养
子进程变成后台进程
若子进程先结束:
父进程如果没有及时回收,子进程变成僵尸进程

进程结束 – exit/_exit

#include
#include
void exit(int status);
void _exit(int status);

Linux应用程序设计_05-进程_第10张图片
|Linux应用程序设计_05-进程_第11张图片

#include
int main()
{
	printf("this process will exit!");
	exit(0);
	printf("never be displayed!");
}

在这里插入图片描述

int main()
{
	printf("Using exit...\n");
	printf("This is the end");
	exit(0);
}

Using exit…
This is the end

int main()
{
  printf("Using _exit...\n");
  printf("This is the end");
  _exit(0);
}

Using _exit…

_exit和exit的区别

_exit()函数的作用最为简单:直接使进程终止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;

exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序。

exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的"清理I/O缓冲"一项。

进程 – exec函数族

(1)exec 函数族说明。
fork()函数用于创建一个子进程,该子进程几乎复制了父进程的全部内容。我们能否让子进程执行一个新的程序呢?exec 函数族就提供了一个在进程中执行另一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代当前进程的数据段、代码段和堆栈段。在执行完之后,当前进程除了
进程号外,其他内容都被替换了。这里的可执行文件既可以是二进制文件,也可以是 Linux 下任何可执行
的脚本文件。

在 Linux 中使用 exec 函数族主要有两种情况。
① 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用 exec 函数族中的任意一个函数
让自己重生。
② 如果一个进程想执行另一个程序,那么它就可以调用 fork()函数新建一个进程,然后调用 exec 函数族中的任意一个函数,这样看起来就像通过执行应用程序而产生了一个新进程(这种情况非常普遍)。

Linux应用程序设计_05-进程_第12张图片

exec函数族使用区别

可执行文件查找方式
表中的前四个函数的查找方式都是指定完整的文件目录路径,而最后两个函数(以p结尾的函数)可以只给出文件名,系统会自动从环境变量“$PATH”所包含的路径中进行查找。
参数表传递方式
两种方式:逐个列举或是将所有参数通过指针数组传递
以函数名的第五位字母来区分,字母为“l”(list)的表示逐个列举的方式;字母为“v”(vertor)的表示将所有参数构造成指针数组传递,其语法为char *const argv[]
环境变量的使用
exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。这里,以“e”(Enviromen)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量

进程 – execl / execlp

  #include  
  int execl(const char *path, const char *arg, …);
  int execlp(const char *file, const char *arg, …);

Linux应用程序设计_05-进程_第13张图片

#include 
#include 

int main()
{
	 /*调用execlp函数,相当于调用了“ps -ef”命令*/
	  if (execlp("ps", "ps", "-ef", NULL) < 0)
      {
	       perror("execlp error!");
	  }
	  return 0;
}

exec函数族使用注意点

在使用exec函数族时,一定要加上错误判断语句

常见的错误原因有:

  1. 找不到文件或路径,此时errno被设置为ENOENT
  2. 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT
  3. 没有对应可执行文件的运行权限,此时errno被设置为EACCESS

进程 – execv / execvp

#include  
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

Linux应用程序设计_05-进程_第14张图片

执行ls命令,显示/etc目录下所有文件的详细信息

  char  *arg[] = {“ls”, “-a”, “-l”, “/etc”, NULL};
  
  if  (execv(“/bin/ls”, arg) < 0) {
     perror(“execv”);
  }  
  
  if  (execvp(“ls”, arg) < 0) {
     perror(“execvp”);
  }  

进程 – system

#include  
int system(const char *command);

Linux应用程序设计_05-进程_第15张图片
定义:
int system(const char * string);
表头文件:
#include
说明:
system()会调用fork()产生子进程, 由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令, 此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD信号会被暂时搁置, SIGINT和SIGQUIT 信号则会被忽略。
返回值:
如果system()在调用/bin/sh时失败则返回127, 其他失败原因返回-1。若参数string为空指针(NULL), 则返回非零值。如果system()调用成功则最后会返回执行shell命令后的返回值, 但是此返回值也有可能为system()调用/bin/sh失败所返回的127, 因此最好能再检查errno来确认执行成功。

#include

int main(int argc, const char *argv[])
{
	system("ls -al /etc/passwd /etc/shadow");
	return 0;
}

在这里插入图片描述

你可能感兴趣的:(Linux应用程序设计)