Linux进程控制知识总结

目录

一:进程标识符(ID)

二:进程操作

2.1创建一个进程

2.2 fork函数出错情况

2.3创建一个共享空间的子进程

2.4退出程序

2.5设置进程所有者

三:执行程序

3.1 exec函数

3.2 执行解释器文件

3.3在程序中执行Shell命令

四:关系操作符

4.1等待进程退出

4.2 等待指定的进程

进程控制

—— 一步

一:进程标识符(ID)

进程ID是用来标识进程的编号,就像身份证一样。不同的进程有不同的ID,可以通过ID来查询进程。进程标识符的类型是pit_t,其本质是一个无符号整型。使用ps命令可以查看当前进程。

每一个进程有6个重要的ID值:

进程ID;

父进程ID;

有效用户ID;

有效组ID;

实际用户ID;

实际组ID;

Linux下使用下列函数可以获得相应的ID:

#include<unistd.h>

pid_t getpid(void);  //得到进程ID

pid_t getppid(void);  //得到父进程ID

uid_t getuid(void);  //得到实际用户ID

uid_t geteuid(void);   //得到有效用户ID

gid_t getgid(void);

gid_t getegid(void);

上述几个函数成功则返回相应的ID,失败则返回-1。

示例:

/*打印相关进程信息*/

#include<stdio.h>

#include<unistd.h>

int main()

{

pid_t pid,ppid,uid,euid,gid,egid;

pid=getpid();

ppid=getppid();

uid=getuid();

euid=geteuid();

gid=getgid();

egid=getegid();

printf("id of current process:%u",pid);

printf("parent id of current process:%u",ppid);

printf("user of current process:%u",uid);

printf("effective user of current process:%u",euid);

printf("group id of current process:%u",gid);

printf("effective group id of current process:%u",egid);

return 0;

}

/*END*/

/*************************分隔符********************************/

二:进程操作

2.1创建一个进程

使用函数fork可以创建一个新进程:

#include<unistd.h>

pid_t fork(void);

返回值有3种情况:

1.对于父进程,fork函数返回新创建的子进程的ID;

2.对于子进程,fork函数返回0。由于系统的0号进程是内核进程,因此子进程的进程号不可能为0。由此区别父进程和子进程。

3.如果出错,返回-1。

fork创建的是子进程,其完全复制了父进程的地址空间的内容,包括堆栈段和数据段的内容。

子进程并没有复制代码,而是和父进程共用代码段。

简言之:子进程地址空间完全和父进程分开。父子进程是两个独立的进程,接收系统调用的机会相等。

示例:

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

int global;  //全局变量,默认初值为0

int main()

{

pid_t pid;

int stack=1;

int *heap;

heap=(int *)malloc(sizeof(int));

*heap=2;

pid=fork();  //创建一个子进程

if(pid<0){

printf("fail to fork!\n");

exit(1);

}else if(pid==0){

global++;

stack++;

(*heap)++;

printf("the chid,global:%d,stack,%d,heap,%d\n",global,stack,*heap);

exit(0);

}    //子进程运行结束

sleep(2);/*由于父子进程并列,被运行的几率相等,因此此处将父进程延时2s,以让子进程先运行*/

printf("the parent,global:%d,stack.%d,heap,%d\n",global,stack,*heap);

return 0;

}

/*END*/

运行结果:

the child,global:1,stack:2,heap:3

the parent,global:0,stack:1,heap:2

可见,在子进程里改变了变量的值,但并没有影响父进程变量的值。上面已经说过,子进程复制父进程地址空间里面的内容,即会将global,stack,heap复制到自己的地址空间,其地址空间是另外开辟的,与父进程的地址空间并列。因此子进程改变变量的值只是在自己的地盘改变,并不会影响到父进程地址空间

里的变量值。

2.2 fork函数出错情况

fork出错将返回-1,有两种情况可能导致fork函数出错:

1.系统中已经有太多的子进程存在;

2.调用fork函数的用户的进程太多了。

下面示例出错即是因为创建过多进程使系统奔溃:

示例:

#include<unistd.h>

#include<stdio.h>

int main()

{

while(1)

fork();

return 0;

}

/*END*/

程序将不断创建新的子进程,直至导致系统奔溃。

2.3创建一个共享空间的子进程

Linux下提供一个和fork函数类似的函数,也可以用来创建一个公用父进程地址空间的子进程,其函数原型如下:

#include<unistd.h>

pid_t vfork();

vfork与fork函数区别如下:

1.vfork函数产生的子进程和父进程完全共享地址空间,包括代码段,数据段和堆栈段,子进程对这些共享资源的修改可以影响到父进程。

2.vfork函数产生的子进程一定比父进程先运行,也就是说,父进程调用了vfork函数之后会等待子进程运行后再运行。

示例:

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

int global;  //全局变量,默认初值为0

int main()

{

pid_t pid;

int stack=1;

int *heap;

heap=(int *)malloc(sizeof(int));

*heap=2;

pid=vfork();  //创建一个子进程

if(pid<0){

printf("fail to fork!\n");

exit(1);

}else if(pid==0){

global++;

stack++;

(*heap)++;

printf("the chid,global:%d,stack,%d,heap,%d\n",global,stack,*heap);

exit(0);

}    //子进程运行结束

sleep(2);/*由于父子进程并列,被运行的几率相等,因此此处将父进程延时2s,以让子进程先运行*/

printf("the parent,global:%d,stack.%d,heap,%d\n",global,stack,*heap);

return 0;

}

/*END*/

运行结果:

the child,global:1,stack:2,heap:3

the parent,global:1,stack:2,heap:3

可见,子进程对变量的修改影响了父进程。这里有点类似指针的概念,子进程就像指向父进程的指针,其直接操作父进程地址空间里面的内容并且有效。

另外,值得注意的是,不要在任何函数(除主函数)的内部调用vfork,这样会导致段错误。(自己思考为何)

2.4退出程序

进程退出时需要用到退出函数:

#include<stdlib.h>

void exit(int status);

参数status表示退出状态,是个整值。利用exit(1),exit(2)...这样的设置可以方便我们调试程序。

另外Linux内核封装了_exit函数,它与exit的主要区别是:exit会做一些善后工作,比如清理I/O缓冲区,释放用户进程的地址空间等;而_exit函数则直接进入内核,释放用户进程的地址空间,所有用户空间的缓冲区内容都将丢失。

2.5设置进程所有者

Linux下可以改变进程用户的ID:

#include<unistd.h>

int setuid(uid_t uid);   //修改实际用户ID和有效用户ID

int seteuid(uid_t uid); //只修改有效用户ID

成功返回0,失败返回-1。

示例:

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

int main()

{

uid_t uid;

uid_t euid;

uid=getuid();

euid=geteuid();

printf("the uid is:%d\n",uid);

printf("the euid is:%d\n",euid);

if(setuid(1000)==-1)

{

printf("fail to set uid!");

exit(1);

}

printf("after changing:\n");

uid=getuid();

euid=geteuid();

printf("the uid is:%d\n",uid);

printf("the euid is:%d\n",euid);

return 0;

}

/*END*/

/*************************分隔符********************************/

三:执行程序

3.1 exec函数

下面我们来看看一个进程如何来启动另一个程序的执行。在Linux中要使用exec函数族。系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)以及环境变量(envp)。exec函数族当然不止一个,但它们大致相同。下面对exec函数做简要介绍:

1)execl()

int execl(const char *path, const char *argv.......);

函数执行成功则不返回,否则返回-1

功能:execl()用于执行参数path字符串代表的文件路径,接下来参数代表执行文件时传递argv,最后一个参数必须以空指针结束。

2)execle()

int execle(const char *path, const char *argv.....,const char *envp[])

功能:执行那个参数path字符代表的文件路径,接下来参数代表执行文件时传递的参数argv,最后一个参数必须指向一个新的环境变量数组,成为新执行程序的环境变量。

3)execlp()

int execlp(const char *path, const char *arg......)

功能:从path环境变量所指目录中查找符合参数file的文件名,找到后执行该文件,接下来参数代表执行文件时传递的argv[0],最后一个参数必须以空指针NULL。

4)execv()

int execv(const char *path, const char *arg[])

功能:执行参数path字符代表的文件路径,第二参数以数组指针来传递给执行文件。

5)execve()

int execve(const char *filename, const char *argv[], const char *envp[])

功能:执行filename字符串代表的文件路径,中间参数利用数组指针来传递给执行文件,最后一个参数为传递给执行文件的新环境变量数组。

6)execvp()

int execvp(const char *filename, const char *argv[])

功能:从path环境变量所指定目录中查找符合参数file的文件名, 找到后执行此文件,第二个参数argv传递给要执行的文件。

当fork创建新进程时,即可使用exec函数执行新的程序,但并不是创建新的进程。一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。

示例:

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

int main(void)

{

pid_t pid;

pid=fork(); //创建子进程

if(pid<0)

{

printf("fail to fork!");

exit(1);

}

else

if(pid==0)  //子进程

{

/*调用exec函数,运行当前目录下的setuid程序*/

if(execvp("hello",NULL)==-1)

{

printf("fail to exec!\n");

exit(0);

}

/*这里应该永远不会执行,因为调用exec后这里的代码被setuid程序取代了*/

printf("the child is not hello!\n");

exit(0);//子进程正常退出

}

printf("the parent!\n");   //父进程

return 0;

}

/*END*/

其中hello为输出“hello world”的可执行程序。

运行输出:

hello world

the parent!

可见运行exec函数后的子进程将被加载的hello程序替换,直至子进程运行结束。

3.2 执行解释器文件

Linux中可执行文件分为两种,一种是二进制可执行文件,这种文件经过编译系统编译链接后生成。另一种是解释器文件,这种文件不是二进制的,而是一般的文本文件。这种文件起始形式为:

#!解释器程序的路径  解释器程序所需要的参数

例如Linux环境下使用的Shell脚本,其脚本的起始形式为:

#!/bin/sh

除去第一行以外的文件内容都被认为是解释器文件的内容,其处理交由定制的解释器程序处理。

此处只是粗略介绍,详细知识请查阅有关资料。

3.3在程序中执行Shell命令

Linux环境下使用system调用Shell指令:

#include<stdlib.h>

int system(const char *cmdstring);

下面是使用ls命令写到文件并读出显示的程序:

示例:

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<fcntl.h>

#define MAX 1024

int main()

{

int fd,n;

char buf[MAX];

if(system("ls > temp.txt")==-1)

{

printf("fail to exec command!\n");

exit(1);

}

if((fd=open("temp.txt",O_RDWR))==-1)

{

printf("fail to open!\n");

exit(1);

}

if((n=read(fd,buf,MAX))==-1)

{

printf("fail to read!\n");

exit(1);

}

buf[n]=‘\0‘;

printf("%s",buf);

return 0;

}

/*END*/

该程序将ls命令执行结果写入temp.txt之中,之后再从temp.txt当中读取输到屏幕显示。其作用效果与直接执行ls命令一样。

system函数的实现:

system函数的执行流程分两步,首先调用system函数的进程创建出一个子进程,并调用wait函数等待子程序执行完毕;然后由这个子进程调用exec函数加载shell运行cmdstring中指定的命令,根据该流程可以编写一个system函数的实现原理性实现程序。

#include<sys/wait.h>

#include<stdio.h>

#include<unistd.h>

int sys(const char *cmdstring)

{

pid_t pid;

int status;

if(cmdstring==NULL) return 1;

pid=fork();

if(pid<0) status=-1;

else if(pid==0)

{

execl("/bin/sh","sh","-c",cmdstring,NULL);

_exit(127);

}

if(waitpid(pid,&status,0)==-1) status=-1;

return status;

}

/*END*/

/*************************分隔符********************************/

四:关系操作符

4.1等待进程退出

Linux下使用wait函数得到子进程结束的信息:

#include<sys/wait.h>

pid_t wait(int *statloc);

调用wait函数会使进程阻塞,直到该进程的任意一个子进程结束。statloc用来保存子进程的返回信息,内核将会取得的子进程结束信息保存在该指针所指向的空间。如果指针为NULL,则表示用户对返回信息并不关心。

返回信息是一个整数,不同的位代表不同的信息:正常结束状态,终止进程的信号编号和暂停进程的信号编号。Linux系统定义了专门的宏,用来判断哪种状态有效并取得相应的状态值。

状态        判断宏              取值宏

正常结束   WIFEXITED(status)    WEXITSTATUS(status)

异常终止   WIFSIGNALED(status)  WTERMSIG(status)

进程暂停   WIFSTOPPED(status)   WSTOPSIG(status)

例如,当一个程序正常退出时,该进程父进程得到它返回信息,此时需要判断:如果WIFEXITED(status)为真,则该进程是正常退出,WEXITSTATUS(status)返回进程结束状态信息。

示例:

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<sys/wait.h>

int main()

{

pid_t pid;

int status;

pid=fork();

if(pid<0){

printf("fail to fork!\n");

exit(1);

}else if(pid==0)

{

printf("exit normally!\n");

exit(0);

}

if(wait(&status)==-1){

printf("fail to wait!\n");

exit(1);

}

if(WIFEXITED(status)==1)

printf("the status is:%d\n",WTERMSIG(status));

return 0;

}

/*END*/

4.2 等待指定的进程

需要等待指定进程需要用到:

#include<sys/wait.h>

pid_t waitpid(pid_t pid,int *statloc,int options);

第一个参数为要等待子进程的ID,第三个是控制选项:

wait函数选项      选项说明

WCONTINUED   当子进程在暂停后继续执行,且其状态尚未报告,则返回其状态

WNOHANG      当等待的进程未结束运行时不阻塞,waitpid函数直接返回

WUNTRACE 当子进程暂停时,并且其状态自暂停以来还未报告过,则返回其状态

僵尸进程:

1). 僵尸进程概念:

就是已经结束了的进程,但是没有从进程表中删除。太多了会导致进程表里面条目满了,进而导致系统崩溃,倒是不占用系统资源。在进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集。

除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会

一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。

2).僵尸进程产生的原因:

每个 Linux进程在进程表里都有一个进入点(entry),核心程序执行该进程时使用到的一切信息都存储在进入点。当用ps命令察看系统中的进程信息时,看到的就是进程表中的相关数据。

当以fork()系统调用建立一个新的进程后,核心进程就会在进程表中给这个新进程分配一个进入点,然后将相关信息存储在该进入点所对应的进程表内。这些信息中有一项是其父进程的识别码。当这个进程走完了自己的生命周期后,它会执行exit()系统调用,此时原来进程表中的数据会被该进程的退出码(exit code)、执行时所用的CPU时间等数据所取代,这些数据会一直保留到系统将它传递给它的父进程为止。由此可见,进程的出现时间是在子进程终止后,但是父进程尚未读取这些数据之前。

3).僵尸进程的查看:

用 top命令,可以看到

Tasks: 123 total,   1 running, 122 sleeping,   0 stopped,   0 zombie

zombie前面的数量就是僵尸进程到数量;

ps -ef

出现:

root     13028 12956 0 10:51 pts/2    00:00:00 [ls] <defunct>

最后有 defunct的标记,就表明是僵尸进程。

4).僵尸进程解决办法:

4.1 改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。 子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行  waitpid()函数为子进程收尸。

这是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD 消息,尽管对的默认处理是忽略,如果想响应这个消息,可以设置一个处理 函数。

4.2 把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给1号进 程init,init始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消 失。

kill -9 `ps -ef | grep "Process Name" | awk ‘{ print $3 }‘`

其中,“Process Name”为处于zombie状态的进程名。

4.3 杀父进程不行的话,就尝试用skill -t TTY关闭相应终端,TTY是进程 相应的tty号(终端号)。但是,ps可能会查不到特定进程的tty号,这时就 需要自己判断了。

4.4 实在不行,重启系统吧,这也是最常用到方法之一。

当一个进程已退出,但其父进程还没有调用系统调用wait(稍后介绍)对其进行收集之前的这段时间里,它会一直保持僵尸状态,利用这个特点,我们来写一个简单的小程序:  

  #include

  #include

  main()

  {

   pid_t pid;

   pid=fork();

   if(pid<0)

   printf("error occurred!n");

   else if(pid==0)

   exit(0);

   else

   sleep(60);

   wait(NULL);

  }

  /*END*/

 

sleep的作用是让进程休眠指定的秒数,在这60秒内,子进程已经退出,而父进程正忙着睡觉,不可能对它进行收集,这样,我们就能保持子进程60秒的僵尸状态。  

  编译这个程序:

  $ cc zombie.c -o zombie

  后台运行程序,以使我们能够执行下一条命令:

  $ ./zombie &

  [1] 1577

  

  列一下系统内的进程:

  $ ps -ax

  ... ...

   1177 pts/0 S 0:00 -bash

   1577 pts/0 S 0:00 ./zombie

   1578 pts/0 Z 0:00 [zombie ]

   1579 pts/0 R 0:00 ps -ax

看到中间的“Z”了吗?那就是僵尸进程的标志,它表示1578号进程现在就是一个僵尸进程。

参考文件:

吴岳等《Linux C程序设计大全》  清华大学出版社 2009.2

CSND博客

www.baidu.com

Linux进程控制知识总结,布布扣,bubuko.com

时间: 2024-12-23 00:33:20

Linux进程控制知识总结的相关文章

Linux进程管理知识整理

Linux进程管理知识整理 1.进程有哪些状态?什么是进程的可中断等待状态?进程退出后为什么要等待调度器删除其task_struct结构?进程的退出状态有哪些? TASK_RUNNING(可运行状态) TASK_INTERRUPTIBLE(可中断等待状态) TASK_UNINTERRUPTIBLE(不可中断等待状态) TASK_STOPPED(进程被其它进程设置为暂停状态) TASK_TRACED(进程被调试器设置为暂停状态) TASK_DEAD(退出状态) 进程由于所需资源得不到满足,从而进入

Linux进程控制编程

一.获取ID #include<sys/types.h> #include<unistd.h> pid_t getpid(void)    获取本进程ID pid_t getppid(void)  获取父进程ID 父进程:现有进程中,创建新的进程. 例:getpid.c #include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { printf("PID=%d\

Linux进程控制——exec函数族

原文:http://www.cnblogs.com/hnrainll/archive/2011/07/23/2114854.html 1.简介 在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是: #include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char

Linux进程控制程序设计

一.进程控制理论基础 进程:是一个具有一定独立功能的程序的一次运行活动.程序是静态的,程序在运行的时候是进程. 1.进程的特点: 动态性:区别于程序的显著特性 并发性:多个进程可以同时执行 独立性:独立的 异步性:进程与进程之间可以进行异步操作 2.进程三态: 进程的ID(PID):标志进程的唯一数字. 父进程ID(PPID) 启动进程的用户ID(UID) 3.进程互斥 进程互斥是指当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用该资源的进程必须等待,直到占用该资源者

linux学习之八---Linux进程基础知识

一.linux进程 linux是一个多用户多任务的操作系统. 多用户是指多个用户可以在同一时间使用计算机: 多任务是指linux可以同时执行几个任务. 进程简单来说就是运行中的程序,Linux系统的一个重要特点是可以同时启动多个进程.根据操作系统的定义:进程是操作系统资源管理的最小单位. 1.Linux进程的概念 进程是一个动态的实体,是程序一次执行过程,并且进程是操作系统资源分配的基本单位. 进程与程序的区别:进程是动态的,程序是静态的:进程是运行中的程序,而程序还是保存在硬盘上的可执行代码.

Linux进程控制(一)

1. Linux进程概述 进程是一个程序一次执行的过程,它和程序有本质区别.程序是静态的,它是一些保存在磁盘上的指令的有序集合:而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建.调度和消亡的过程,是Linux的基本调度单位.那么从系统的角度看如何描述并表示它的变化呢?在这里,是通过进程控制块(PCB)来描述的.进程控制块包含了进程的描述信息.控制信息以及资源信息,它是进程的一个静态描述. 内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程序,

六、Linux进程控制

1. Linux进程概述 进程是一个程序一次执行的过程,它和程序有本质区别. 程序是静态的,它是一些保存在磁盘上的指令的有序集合:而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建.调度和消亡的过程,是Linux的基本调度单位. 那么从系统的角度看如何描述并表示它的变化呢?在这里,是通过进程控制块(PCB)来描述的.进程控制块包含了进程的描述信息.控制信息以及资源信息,它是进程的一个静态描述. 内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程

Linux进程控制(二)

1. 进程的创建 Linux下有四类创建子进程的函数:system(),fork(),exec*(),popen() 1.1. system函数 原型: #include <stdlib.h> int system(const char *string); system函数通过调用shell程序/bin/sh –c来执行string所指定的命令,该函数在内部是通过调用execve("/bin/sh",..)函数来实现的.通过system创建子进程后,原进程和子进程各自运行,

【转载】linux进程控制-exec系列 exec系统调用

inux进程控制-exec系列 说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是: #include <unistd.h>int execl(const char *path, const char *arg, ...);int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, ...,