Linux进程管理与程序开发2

8.2进程管理及控制

8.2.1创建进程

1.fork函数
在Linux环境下,创建进程的主要方法是调用fork()函数。Linux下所有进程都有init(PID为1),直接或间接创建。
extern __pid_t fork(void);
若执行成功,在父进程中将返回在进程的PID,类型为pid_t,子进程将返回0,以区别父子进程。
若执行失败,则在父进程中返回-1,错误原因储存在errno中。
fork()函数调用成功后,将为子进程申请PCB和用户内存空间。子进程会复制父进程几乎所有信息,在用户空间将复制父亲用户空间的所有数据(代码段、数据段、BSS、堆、栈),复制父进程内核空间PCB中绝大部分信息。子进程从父进程继承下列属性:有效用户号/组号、进程组号、环境变量、对文件的执行时关闭标志、信号处理方式设置、信号屏蔽集合、当前工作目录、根目录、文件模式掩码、文件大小限制、打开的文件描述符(共同用一个文件表项)。

2.创建子进程应用示例
子进程在创建后和父进程同时执行,竞争系统资源,谁先执行有调度算法决定,子进程的执行位置为fork的返回位置,下面是调用fork()函数的例子。

vicli@www.100ask.org:~/2_linux_program/_8_process$ cat fork_basic.c 
   #include "stdio.h"
   #include "stdlib.h"
   #include "unistd.h"
   #include "sys/types.h"
  
   int main(int argc, char *argv[])
   {   
      pid_t pid;
      if((pid = fork()) == -1){
          printf("fork error\n");
      }
      printf("fork success\n");
      return 0;
   }

vicli@www.100ask.org:~/2_linux_program/_8_process$ ./fork_basic 
fork success
fork success

从以上进程可以看出,fork函数后的代码也在子进程中。实际上,其他代码也在子进程的代码段中,只是子进程执行位置为fork返回位置,其之前的代码无法执行罢了。
下面是将父子进程执行的代码分开的示例,返回值大于0的(返回PID)的代码在父进程中运行,返回值为0则在子进程中运行。

vicli@www.100ask.org:~/2_linux_program/_8_process$ sudo ./fork_chdpat_exp 
parent pid = 4977
parent's pid = 4976
children pid = 0
vicli@www.100ask.org:~/2_linux_program/_8_process$ cat fork_chdpat_exp.c 
#include "unistd.h"
#include "stdio.h"
#include "stdlib.h"
#include "sys/types.h"
 
int main()
{
     pid_t pid;
     if((pid = fork()) == -1){
         perror("fork error");
         exit(EXIT_FAILURE);
     }
     else if(pid == 0){
         printf("children pid = %d\n", pid);
     }
     else{
         printf("parent pid = %d\n", pid);
    	 pid = getpid();
		 printf("parent's pid = %d\n", pid);
	 }
     return 0;
 }

3.子进程对父进程文件流缓冲区的处理
文件流缓冲区的资源位于用户空间,因此,在创建子进程时,子进程的用户空间将复制父进程的用户空间所有信息,显然,也包括流缓冲区的内容。如果流缓冲区中有临时信息,则同样复制到子进程的用户空间流缓冲中。
4.子进程对父进程打开的文件描述符的处理
fork函数创建子进程后,子进程将复制父进程的代码段、数据段、BSS段、堆、栈和文件描述符,而对于文件描述符关联的内核文件表项(即struct file结构),采用共享的方式。
示例代码:

vicli@www.100ask.org:~/2_linux_program/_8_process$ cat fork_descriptor.c 
#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
#include "fcntl.h"
#include "string.h"
#include "stdlib.h"
#include <sys/wait.h>

int main(int argc, char *argv[])
{
	pid_t pid;
	int fd;
	int i = 1;
	int status;
	char *ch1 = "hello";
	char *ch2 = "world";
	char *ch3 = "IN\n";

	if((fd = open("test.txt", O_RDWR|O_CREAT, 0644)) == -1){
		perror("parent open");
		exit(EXIT_FAILURE);
	}
	if((write(fd, ch1, strlen(ch1))) == -1){
		perror("parent write");
		exit(EXIT_FAILURE);	
	}
	if((pid = fork()) == -1){
		perror("fork");
		exit(EXIT_FAILURE);	
	}
	else if(pid == 0){
		i = 2;
		printf("in child\n");
		printf("i = %d\n", i);
		if(write(fd, ch2, strlen(ch2)) == -1){
			perror("child write");
			exit(EXIT_FAILURE);	
		}	
		return 2;
	}
	else{
		sleep(1);
		printf("in parent\n");
		printf("i = %d\n", i);
		if(write(fd, ch3, strlen(ch3)) == -1){
			perror("parent write");	
		}
//--当子进程退出的时候,内核会向父进程SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)
//--子进程退出时,内核将子进程置为僵尸状态,这个进程成为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态
//--父进程查询子进程的退出状态可以用wait/waitpid函数	
		wait(&status);
		printf("wait = %d\n", status);
		return 0;
	}
}
vicli@www.100ask.org:~/2_linux_program/_8_process$ sudo ./fork_descriptor 
[sudo] password for vicli: 
in child
i = 2
in parent
i = 1
wait = 512

5.结合vfork()测试全局数据段与BSS段使用策略
vfork()函数创建新进程时并不复制父进程的地址空间,而是在必要的时候才申请到新的存储空间。如果子进程只执行exec()函数,则使用fork()从子进程复制到子进程的数据空间将不被使用,这样效率非常低。这样效率非常低,从而使得vfork()非常有用。根据父进程数据空间的大小,vfork比fork可以很大程度上提高性能。vfork只在需要时复制,而一般采用与父进程共享资源的方式处理。
vfork在子进程中返回0.在父进程中返回子进程的进程号。
在以下程序中使用vfork创建子进程,子进程对全局变量和父亲进程的局部变量进行修改,然后在父子进程中分别打印修改后变量,由结果可以看出父子进程共享数据空间。

vicli@www.100ask.org:~/2_linux_program/_8_process$ cat vfork_fork_cmp.c 
#include "stdio.h"
#include "stdlib.h"
#include "sys/types.h"
#include "unistd.h"
#include "error.h"

int glob = 666;
int main(int argc, char *argv[])
{
	int var;
	pid_t pid;
	var = 88;
	printf("beginning: glob = %d\t, var = %d\n", glob, var);

	if((pid = vfork()) < 0){
		perror("vfork");
		exit(EXIT_FAILURE);	
	}
	else if(pid == 0){
		printf("in children\n");
		var++;
		glob++;
		printf("in children glob = %d\t, var = %d\n", glob, var);
		_exit(0);
	}
	else{
		printf("in parent glob = %d\t, var = %d\n", glob, var);
		return 0;
	}
}
vicli@www.100ask.org:~/2_linux_program/_8_process$ sudo ./vfork_fork_cmp 
beginning: glob = 666	, var = 88
in children
in children glob = 667	, var = 89
in parent glob = 667	, var = 89

6子函数调用vfork创建子进程

root@book-virtual-machine:/home/vicli/2_linux_program/_8_process# cat vfork_return.c 
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "signal.h"
#include "fcntl.h"
#include "sys/syslog.h"
#include "sys/param.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "time.h"
#include "string.h"

void test()
{
pid_t pid;
int status;
pid = vfork();
if(pid == -1){
exit(EXIT_FAILURE);
}
else if(pid == 0){
printf("1: in child pid = %d, ppid = %d\n", getpid(), getppid());
return ;
}
else{
//wait(&status);
printf("2:in parent pid = %d, ppid = %d\n", getpid(), getppid());
//return ;
}
}

void fun()
{
int i;
int buf[100];
memset(buf, 0, 100);
printf("3: in fun pid = %d, ppid = %d\n", getpid(), getppid());
}
int main()
{
test();
fun();
}
root@book-virtual-machine:/home/vicli/2_linux_program/_8_process# ./vfork_return 
1: in child pid = 5239, ppid = 5238
3: in fun pid = 5239, ppid = 5238
2:in parent pid = 5238, ppid = 5159
vfork_return: cxa_atexit.c:100: __new_exitfn: Assertion `l != NULL' failed.
Aborted (core dumped)


在运行过程中出现了段错误,原因分析:下图给出了此程序的调用过程:
在子函数调用vfork创建子进程栈使用示意

1.调用main函数,申请栈空间,没问题。
2.调用test函数申请栈空间,此程序调用vfork函数,因为vfork创建的父子进程共享栈空间,而子程序先执行,因此可以正常运行,然后继续执行后返回,将清理栈空间。
3.子进程再调用fun函数,将覆盖原来的test函数栈空间,然后继续执行fun函数,没有异常。
4.子进程退出后,父进程从vfork的返回处开始执行代码,没有问题,但是返回时栈空间已经不存在,因此出现栈错误。

8.2.2在进程中运行新的代码

1.函数功能介绍及应用
用fork创建子进程后,如果希望子进程中运行新的程序,则可以调用execX系列函数。当进程调用exec系列函数中的任意一个时,该进程的用户空间资源(代码、数据、BSS、堆、栈)完全由新程序代替。因为调用exec并不创建新的进程,如果无特殊提示,进程的内核信息基本不做修改。
这些函数的区别为:指示新程序的位置是使用路径还是文件名,如果使用文件名,则在系统的$PATH环境变量所描述的路径中搜索该程序;在使用参数时,是使用参数列表的方式还是使用argv[]数组的方式。
execX系列函数比较

函数使用文件名使用路径名使用参数表(函数出现字幕l)使用argv(函数出现字母v)
execl
execlp
execle
execv
execvp
execve

execl函数声明:
extern int execl(__const char * __path, __const char *arg, …);
execl用来执行参数path字符串所指向的程序,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针以标识参数列表为空。

execl("/bin/ls", "ls", "-l", "/home", (char *)0);

execle函数声明

extern int execle(__const char * _path, __const char * arg, ...);

execle用来执行path字符串所指向的程序,第二个及以后参数代表文件执行时传递的参数列表,最后一个参数必须指向一个新的环境变量数组,即新执行的程序的环境变量。

char *env = "  ";
execl("/bin/ls", "ls", "-l", "/home", (char *)0, env);

execlp函数声明

extern int execlp(__const char *__file, __const char * arg, ...)

execlp会从$PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个参数及以后的参数代表执行文件时传递的参数列表,最后一个参数必须用到空指针。

execl("/ls", "ls", "-l", "/home", (char *)0);

execv函数声明

extern int execv(__const char *__path, char * __const __argv[]);

execv用来执行参数path字符串所指向的程序,第二个参数为数组指针维护的程序参数列表。

char *argv[] = {"ls", "-l", "/home", (char *)0};
execv("/bin/ls", argv);

execvp函数声明

extern int execvp(__const char *__file, char *__const __argv[]);

execvp会从$PATH环境变量所指的目录中查找文件名为第一个参数所指示的字符串,找到后执行该文件,第二个及以后的参数代表执行文件时传递的参数列表,最后一个成员必须为NULL。

char *argv[] = {"ls", "-l", "/home", (char *)0};
execv("/ls", argv);

除了以上函数外,system()以新进程的方式运行一个程序,然后结束。system()函数用来创建新进程,并在此新进程中运行新进程,直到新进程结束后,才继续运行父进程。子进程结束后,会返回退出状态(如wait函数一样)。

extern int system(__const char * __command);

示例代码

root@book-virtual-machine:/home/vicli/2_linux_program/_8_process# cat system_exp.c 
#include "stdio.h"
#include "stdlib.h"
#include "sys/wait.h"

int main(int argc, char **argv)
{
	int status;
	status = system("pwd");
	if(!WIFEXITED(status)){
		printf("abnormal exit\n");	
	}	
	else
		printf("the exit status is %d\n", status);

	return 0;
}
root@book-virtual-machine:/home/vicli/2_linux_program/_8_process# ./system_exp 
/home/vicli/2_linux_program/_8_process
the exit status is 0

2.执行新代码对打开文件的处理

在执行exec系列函数时,默认情况下,新代码可以使用在原来代码打开的文件描述符,即执行exec系列函数时,并不关闭原来打开的文件。但如果调用以下代码:
fcntl(fd, F_SETFD, FD_CLOEXEC);
即关闭FD_CLOEXEC项,则在执行execX系列函数后将关闭原来打开的文件描述符。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值