进程控制(1)

一、  

  进程是一个动态的实体,是程序的一次执行过程。是操作系统分配资源的基本单位。进程是动态的,程序是静态的。进程是运行中的程序,程序是保存在硬盘上的可执行代码。

    1.fork函数是创建一个新进程的唯一方法,vfork虽然也可以创建进程,但其在创建时还是调用了fork函数。
    fork函数有两个返回值,即调用用一次返回两次。成功调用fork函数以后会分裂为两个进程,一个为原来的父进程,其返回值为子进程的ID,另一个进程为子进程,其返回值为0。调用失败会返回-1。(失败原因多为父进程拥有子进程数目超过限制)
    fork函数调用以后,父进程或者子进程的执行顺序是不确定的,主要取决于内核的调度算法。

//fork.c
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int main() { pid_t pid; int k; char *p; printf("Process Creation Study!\n"); pid = fork(); switch(pid) { case 0: p = "子进程运行中"; k = 3; break; case -1: perror("Process creation failed!\n"); break; default: p = "父进程运行中"; k=4; //return 0; break; } while(k) { puts(p); sleep(1); --k; } return 0; }

 运行结果


    子进程与父进程的异同点
    ①子进程拥有自己唯一的ID
    ②fork的返回值不同,父进程fork返回子进程ID,子进程返回0
    ③不同的父进程ID。子进程的父进程ID为创建它的父进程ID
    ④子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符
    ⑤子进程不继承父进程设置的文件锁
    ⑥子进程不继承父进程设置的警告
    ⑦子进程的未决信号集被清空
    2.孤儿进程
      如果一个父进程先于子进程结束(凉凉),子进程就成为了一个孤儿进程,它由init进程收养,成为init进程的子进程。(init的进程ID为 1 )
    3.vfork函数
    vfork函数与fork函数的异同
    ①vfork与fork一样都是调用一次,返回两次
    ②使用fork函数创建一个子进程时,子进程会完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。而使用vfork函数创建一个子进程时,操作系统并不将父进程的地址空间完全复制到子进程,用vfork创建的子进程共享父进程的地址空间,也就是说子进程完全运行在父进程的地址空间上。子进程中修改任何数据父进程均可见。
    ③使用fork创建一个子进程时,父子进程执行顺序取决于系统的调度算法,但是vfork会先将子进程执行结束后再执行父进程如果子进程中有依赖父进程的某个行为,则会导致死锁。
    fork和vfork相比,是花销较大的系统调用,vfork可大大节省系统开销。

使用fork时

//fork.c
#include<stdlib.h>
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int globv = 7; int main() { pid_t pid; int var = 1, i; printf("fork 和 vfork的不同\n"); pid = fork(); //pid = vfork(); switch(pid) { case 0: i = 3; while(i-- > 0) { printf("子进程运行中\n"); ++globv; ++var; sleep(1); } printf("子进程的 globv = %d, var = %d\n", globv, var); break; case -1: printf("糟糕,进程创建失败了!\n"); exit(0); default: i = 5; while(i-- > 0) { printf("父进程运行中\n"); ++globv; ++var; sleep(1); } printf("父进程的 globv = %d, var = %d\n", globv, var); break; } exit(0); }

 运行结果

将pid=fork()注释掉,使用pid=vfork()的运行结果

 


    4.创建守护进程(daemon)
    守护进程(daemon)是指在后台运行的、没有控制终端与之相连的进程。他独立于控制终端,通常周期性的执行某种任务。Linux大多数服务器就是用守护进程的方式实现的。
    守护进程的启动方式有多种:它可以在Linux系统启动时从脚本/etc/rc.d中启动;也可以由作业规划进程crond启动;还可以由用户终端(shell)启动。
    编写创建进程守护进程的程序时,要避免产生不必要的交互。编写创建守护进程的要点如下:
    ·让进程在后台运行。方法是调用fork产生一个子进程,然后使得父进程退出。
    ·调用setsid创建一个新的对话期。控制终端,登陆会话和进程组通常是从父进程继承下来的,守护进程要摆脱它们,不受它们的影响,其方法是调用setsid使进程成为一个会话组长。
    ·禁止进程重新打开控制终端。此时,进程已经成为一个无终端的会话组长,但是它可以重新申请打开一个终端。为了避免这种情况的发生,可以通过使进程不再是会话组长来实现。即再使用一次fork函数,然后让父进程先退出。
    ·关闭不再需要的文件描述符。创建的子进程从父进程继承了打开的文件描述符。如果不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及无法预料的错误。先得到最高文件描述符值,然后使用循环程序一个个全关闭。for(i=0; i<NOFILE, close(i++))
    ·将当前目录更改为根目录,当守护进程当前工作目录在一个装配文件系统中时,该文件系统不能被拆卸,一般要将工作目录更改为根目录。
    ·将文件创建时使用的屏蔽字设置为0。进程从创建它的父进程那里继承的文件创建屏蔽字可能会拒绝某些许可权。为防止这一点,使用umask(0)将屏蔽字清零。
    ·处理SIGCHLD信号。这步非必须的。但是对于一些进程,特别是服务器进程往往在请求到来之时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie),从而占用系统资源。如果父进程等待子进程结束,将会将增加父进程负担,影响服务器的并发性能。Linux下可以简单的将SIGCHLD信号的操作设为SIG_IGN。这样,子进程结束时不会产生僵尸进程。

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<signal.h>
#include<sys/param.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<time.h>
#include<syslog.h>
int init_daemon(void)
{
	int pid;
	int i;
	//忽略终端 I/O信号,STOP信号
	signal(SIGTTOU, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	signal(SIGTSTP, SIG_IGN);
	signal(SIGHUP, SIG_IGN);

	pid = fork();
	if(pid > 0)
		exit(0);//让父进程先行告退,使子进程成为后台进程
	else if(pid < 0)
		return -1;
	//再次建立一个新的k进程组,在新进程组内,子进程成为进程组首进程,使该进程脱离所有终端
	setsid();
	//再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时使其无法再打开一个新的终端
	pid = fork();
	if(pid > 0)
		exit(0);
	else if(pid < 0)
		return -1;
	//关闭从父进程继承的所有文件描述符
	for(i=0; i<NOFILE; close(i++));
	//更改工作目录为根目录,使得进程不与任何文件系统联系
	chdir("/");
	//将文件屏蔽字设置为0
	umask(0);
	//	忽略SIGCHLD信号
	signal(SIGCHLD, SIG_IGN);
	return 0;
}

int main()
{
	time_t now;
	init_daemon();
	syslog(LOG_USER|LOG_INFO, "测试守护进程!\n");
	while(1)
	{
		sleep(8);
		time(&now);
		syslog(LOG_USER|LOG_INFO, "系统时间:\t%s\t\t\n", ctime(&now));
	}
}

 ps -ef可以看到创建守护进程

在  /var/log/message 内可以看到打印的时间信息

 


    5.进程退出
    进程退出表示进程即将结束运行。在Linux系统中进程退出的方法分为正常退出和异常退出两种。其中正常退出的方法有三种,异常退出的方法有两种。
    (1)正常退出
      ①在main函数中return
      ②调用exit函数
      ③调用_exit函数
    (2)异常退出
      ①调用abort函数
      ②进程收到某个信号,而该信号使程序终止
    但是无论哪一种退出,最终都会执行内核中的同一段代码。这段代码用来关闭进程所有打开的文件描述符,释放它所占用的内存和其他资源。
    各种退出方法的比较
    ·exit和returnf的区别:exit是一个函数,有参数;而return是由函数执行完以后的返回。exit把控制权交给系统,而return将控制权交给调用函数。
    ·exit和abort的区别:exit是正常终止进程,而abort是异常终止。
    ·exit(int exit_code):exit中的参数exit_code为0代表正常终止,若为其他值表示程序执行过程中有错误发生,如溢出,除数为0.
    ·exit()和_exit()的区别:exit在头文件stdlib.h中声明,而_exit()声明在unistd.h中。两个函数都可正常退出程序,但是_exit()会执行后立即返回给内核,而exit()要先执行一些清除操作,然后再将控制权交还内核。

    父进程的终止先后顺序不同会产生不同的效果。父进程先与子进程退出,子进程就成了孤儿进程,被init进程收养。当子进程先于父进程结束,而父进程又未使用wait函数等待子进程结束,子进程进入僵死状态,并且会一直保持下去除非系统重启。子进程处于僵死状态时,内核只保存该进程的一些必要信息以备父进程所需。此时子进程始终占有者资源,同时也减少了系统可以创建的最大进程数。如果子进程先于父进程结束,且父进程使用wait或waitpid函数,则父进程会等待子进程结束。

转载于:https://www.cnblogs.com/area-h-p/p/11018036.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值