unix 进程管理

一、进程

	操作系统系统的任务之一
	1. 进程与程序
	(1) 进程是动态的运行中的程序,是动态的。 
	一个运行着的程序,可能有多个进程。进程在操作系统中执行特定的任务。
	(2)程序是存储在磁盘上, 包含可执行机器指令和数据的静态实体。
	 进程或者任务是处于活动状态的计算机程序。
	 一个程序可以被多次运行 对应多个进程。
	 一个程序中可以开辟多个进程。
	2.进程的分类
		交互进程
		批处理进程
		守护进程 总是活跃的一般是后台运行
	3.查看进程
		ps  aux| -elf  静态显示
		     top	   动态显示
 	进程是内存分配的最小单位     内存分配面向进程
    线程是操作系统调度的最小单位		操作系统的任务调度是面向线程的
    函数是C语言程序的基本组成单位
 	进程标识符 getpid( ) 
			  getppid( )父进程进程号

二.fork( )/vfork( )

	子进程独立被系统调用 父子进程独立运行
	父子进程没有先后关系 谁先执行不确定
	父子进程结束也没有先后关系 不确定
	如果子进程先于父进程结束 那么子进程将变为僵尸进程
	如果父进程先于子进程结束 那么子进程将变成孤儿进程 被1(init)收养
(1) 创建一个子进程,失败返回-1
(2) 调用一次,返回两次,分别在父子进程中返回子进程的PID和0。 
用返回值的不同,可以分别为父子进程编写不同的处理分支。
(3)子进程是父进程的副本 
   子进程获得父进程数据段和堆栈段(包括I/O流缓冲区)的拷贝,但子进程共享父进程的代码段。
   申请的内存在父子进程要分别释放 打开的文件分别关闭
(4) 函数调用后父子进程各自继续运行 
 其先后顺序不确定, 某些实现可以保证子进程先被调度。
(5) 共享文件表 
 函数调用后,父进程的文件描述符表(进程级)也会被复制到子进程中,二者共享同一个文件表(内核级)#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(){
	pid_t id = fork();//调用一次 返回两次 两个结果 
	if(id == -1){
		printf("%s\n",strerror(errno));	
		return -1;
	}
	if(id == 0){//子进程中 fork函数返回了0
		printf("我是子进程:%d,父进程为:%d!\n",getpid(),getppid());
	}else{//父进程中fork函数返回了创建的子进程的进程ID
		printf("我是父进程:%d,创建了一个子进程:%d\n",getpid(),id);
	}
	printf("Hello world!\n");
	getchar();	
	return 0;	
}

VirtualBox:~/unix/day04$ ./a.out
我是父进程:3305,创建了一个子进程:3306
Hello world!
我是子进程:3306,父进程为:3305!
Hello world!

vfork

	vfork与fork的区别:
	(1) 调用vfork创建子进程时并不复制父进程的地址空间 
	子进程可以通过exec函数族,直接启动另一个进程替换自身,进而提高进程创建的效率。
	(2) vfork调用之后,子进程先被调度 ,vfork 一定会保证子进程先执行。
	
	一般会用exec函数簇(系列函数)来取代当前进程
    vfork创建的子进程如果没有用exec系列函数创建进程取代,需要用exit系列函数来结束进程,用return无法结束
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(){
	pid_t id = vfork();
	if(id == 0){//exec系列函数来创建进程替换当前进程
		sleep(1);
		printf("子进程:%d\n",getpid());
		//return 0; 无法退出
		exit(0);
	}else{
		printf("父进程:%d\n",getpid());	
	}
	
	return 0;	
}

三、进程的正常退出

1.return或者exit

return x;
等价
exit (x);


2. 调用标准C语言的exit函数。
•1) 调用进程退出 
	 其父进程调用wait/waitpid函数返回status的低8位。
•2) 进程退出之前 
	 先调用所有事先通过atexit/on_exit函数注册的函数, 
	 冲刷并关闭所有仍处于打开状态的标准I/O流, 
	 删除所有通过tmpfile函数创建的文件。
•3) 用EXIT_SUCCESS/EXIT_FAILURE常量宏(可能是0/1)作参数,调用exit()函数表示成功/失败,提高平台兼容性。
•4) 该函数不会返回。
•5) 该函数的实现调用了_exit/_Exit函数。

3. 调用_exit/_Exit函数
#include <unistd.h>
void _exit (int status);
该函数有一个完全等价的标准C版本:
#include <stdlib.h>
void _Exit (int status);

四、wait( )/waitpid( )

wait( )
int main(){
	pid_t id = fork();
	if(id == 0){//子进程
		printf("子进程:%d\n",getpid());
		int retdata = 0;
		scanf("%d",&retdata);
		//return retdata;
		exit(retdata);//_exit(retdata);_Exit(retdata);
	}
	int status = 0;
	int childid = wait(&status);//等待子进程结束 如果有一个子进程已经结束,立即返回,如果没有子进程结束,则等待 阻塞
	//wait参数 用于获取 子进程结束的返回值的低8位  但是不会把返回的结果放于低8位    
	if(WIFEXITED(status)){
		printf("有一个子进程%d正常结束了,返回信息为:%d %d\n",childid,status>>8,WEXITSTATUS(status));
	}else{
		//printf("有一个子进程非正常结束!\n");	
		if(WIFSIGNALED(status)){
			printf("子进程%d是被%d信号异常终止的!\n",childid,WTERMSIG(status));	
		}
	}
	if(WIFSTOPPED(status)){
		printf("子进程暂停运行!\n");	
	}
	return 0;	
}
	如果同时存在多个子进程,又需要等待特定的子进程,可使用waitpid函数 其pid参数
	1. -1 - 等待任一子进程,此时与wait函数等价。
	2.> 0 - 等待由该参数所标识的特定子进程。
	3.  0 - 等待其组ID等于调用进程组ID的任一子进程,即等待与调用进程同进程组的任一子进程。
	4.<-1 - 等待其组ID等于该参数绝对值的任一子进程,即等待隶属于特定进程组内的任一子进程。

waitpid


void showStatus(int status){
	if(WIFEXITED(status)){
		printf("进程正常结束,返回值为:%d\n",WEXITSTATUS(status));	
	}else if(WIFSIGNALED(status)){
		printf("进程异常终止,终止信号为:%d\n",WTERMSIG(status));	
	}
	
}
int main(){
	pid_t ids[5];
	int i;
	for(i=0;i<5;i++){
		ids[i] = fork();
		if(ids[i]==-1){
			perror("fork");
			return -1;
		}
		if(ids[i] == 0){//子进程跳出循环
			break;	
		}
	}

	if(i<5){//子进程
		printf("******我是子进程:%d, 是第%d个进程!\n",getpid(),i+1);
		sleep(i*10);
		printf("******我是子进程:%d, 我死了!\n",getpid());
		return i+1;
	}
	
	int status = 0;
	for(i=0;;i++){
		pid_t inid = 0;
		printf("[-1:任意一个子进程 0:同一个进程组的任意子进程 >0:指定子进程]:");
		scanf("%d",&inid);
		//pid_t id = waitpid(inid,&status,0);	
		pid_t id = waitpid(inid,&status,WNOHANG);//非阻塞模式 
		//如果等待的子进程目前没有结束 直接返回
		if(id == -1){
			--i;
			if(errno == ECHILD){//errno==ECHILD 表明没有子进程了
				printf("所有的子进程都已退出!\n");	
				break;
			}
			continue;
		}else if(id == 0){
			printf("等待的子进程没有结束!\n");	
		}
		printf("有一个子进程:%d结束了,",id);
		showStatus(status);
	}
	return 0;	
}

五、exec

exec函数族(系列函数)
int execl(const char *path,const char *arg,..);
		path 执行的程序名(命令名)带路径的
		arg,...: main函数参数列表  最后一个参数一定是NULL
int execlp(const char *file,const char *arg,...)
		file 执行的程序名 如果不带路径,则从path环境变量中查找该程序
		可以带路径
		p:程序可以不带路径 从path环境变量指定的目录下查找
int execle(const char *path,const char *arg,...,char *const envp[ ])
		e:带环境列表
		l:传arg是用可变长参数列表
int execv(const char *path,char *const argv[ ])
		v:传arg时用数组
int execvp(const char *file,char *const argv[])
int execvpe(const char *file,char *const argv[ ],char *const envp[ ] )
int execve(const char *filename,char *const argv[],char *const envp[])

exec一般不会单独使用 一般结合vfork来使用
int main(){
	函数名中没有p  程序必须带路径
	l: 参数以可变参数列表形式传递
	int ret = execl("/bin/ls","~","-l","-a",NULL);//excel创建一个新的进程替换掉当前进程
	int ret = execlp("ls","~","-l","-a",NULL);//命令可以不带路径 PATH
	int ret = execlp("/bin/ls","~","-l","-a",NULL);
	char* argv[] = {"~","-l","-a",NULL};
	char* envp[] = {"NAME=WD","AGE=18","PATH=/usr/bin:/bin/",NULL};
		v: 参数列表用数组传递
	int ret = execv("/bin/ls",argv);
	int ret = execvp("ls",argv);
	int ret = execle("test","test","a","b","123",NULL,envp);
	char* args[] = {"test","name","afbc","123",NULL};
	int ret = execve("test",args,envp);
	int ret = execvpe("./test",args,envp);
	if(ret == -1){
		perror("execl");	
		return -1;
	}
	printf("main end!\n");//不会执行  exec函数会替换掉当前函数
	return 0;	
}

六、守护进程

独立启动守护进程
	随系统启动而启动,启动后一直常驻内存
	所以会一直占用系统资源
	一直在运行  优点 当外界有要求时 响应速度快
	此类守护进程通常保存在 /etc/rc.d/init.d
超级守护进程
	系统启动时 由一个统一的守护进程xinet来负责管理一些进程
	当外界有要求的时候 需要通过xinet的转接才可以唤醒被xinet管理的进程
优点: 最初只有xinet守护进程占用资源 
	 其他的内部服务并不一直占系统资源
	 只有请求到来时 服务进程才会被唤醒 
编写守护进程的步骤
1.创建子进程 并且要求父进程退出
	让init(1)进程收养子进程
2.在子进程中创建新对话
	setsid( )函数 创建一个新的会话
	让进程摆脱原会话的控制
	让进程摆脱原进程组的控制
	让进程摆脱原控制终端的控制
3.改变当前目录为根目录
	chdir(“/”);
4.重设文件权限掩码
	umask(0)
5.关闭文件描述符
	for(i=0;i<MAXFILE;i++)
	close(i);
6.守护进程退出处理
	信号处理发
守护进程的特点
	1.都是具有超级用户的权限 
	2.父进程都是init进程
	3.都不用控制终端 其TTY列以”?” 标识TPGID为-1
	4.守护进程都是各自进程组会话过程的唯一进程
int main(){
	pid_t id = fork();
	if(id == -1){
		perror("fork");
		return -1;
	}
	if(id > 0){//父进程
		return 0;  //父进程
	}
	printf("%d\n",getpid());
	setsid();
	chdir("/");
	umask(0);
	int i;
	for(i=0;i<3;i++)
		close(i);
	//signal(SIGINT,sigint_handler);//如果进程接收到SIGINT函数 去执行对应函数
	signal(2,sigint_handler);//如果进程接收到SIGINT函数 去执行对应函数
	//异步
	while(running){
		int fd = open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0644);
		if(fd == -1){
			perror("open");
			return -1;
		}
		char *p = "这是一个守护进程!\n";
		write(fd,p,strlen(p));
		close(fd);
		sleep(3);
	}
		return 0;	
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值