精华内容
下载资源
问答
  • 2021-11-08 16:39:40

    创建父子2个进程A,B。进程A不断获取用户从键盘输入的字符串或整数,通过信号机制传给进程B。如果输入的是字符串,进程B将其打印出来;如果输入的是整数,进程B将其累加起来,并输出该数和累加的和。当累加和大于100时结束子进程,子进程输出“My work done!”后结束,然后父进程也结束。编写程序实现,并给出测试结果。

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <strings.h>
    
    main(){
    	int r, p, fd1[2], fd2[2];
    	char buf[50], s[50], massr[50];
    	char flag[50] = "My work is dong!";
    	char massw[50] = "My work done!";
    	int i = 0, sum = 0, num = 0;
    	pipe(fd1);		//父进程建立管道
    	pipe(fd2);
    	while((p = fork()) == -1);	
    	if(p == 0){		//由子进程P1返回,执行子进程P1
    		close(fd1[1]);		//关闭管道1写入端 
    		close(fd2[0]);		//关闭管道2读出端
    		write(fd2[1], flag, 50);
    		while(read(fd1[0], s, 50)){
    			while(s[i] >= '0' &&s[i] <= '9'){
    				i++;
    			}
    			if(i == strlen(s)){
    				num = atoi(s);
    				sum += num;
    				printf ("message is number : %d\n", num);
    				printf ("sum of number : %d\n", sum);
    				if(sum > 100){
    					write(fd2[1], massw, 50);
    					printf ("My work done!\n");
    					sleep(1);
    					break;
    				}else{
    					write(fd2[1], flag, 50);
    					sleep(1);
    				}	
    			}else{
    				printf ("message of child process receive:\n");
    				printf ("%s\n", s);
    				write(fd2[1], flag, 50);
    				sleep(1);
    			}	 
    		}
    		exit(0);     //关闭 	
    	}else{
    		close(fd1[0]);
    		close(fd2[1]);
    		while(read(fd2[0], massr, 50)){
    			if(strcmp(massr, "My work done!") == 0) {
    				printf ("receive My work done, program is exiting!\n");
    				break;
    			}else{
    				printf ("please input your message!\n");
    				scanf("%s", buf);
    				write(fd1[1], buf, 50);	//把buf中的50个字符写入管道 
    				sleep(1);		//睡眠5秒,让子进程读
    			}
    		}
    		printf ("exited!\n");
    		exit(0);	
    	}
    	
    } 

    结果:

    更多相关内容
  • 进程编程 一、服务器并发访问的问题        服务器按处理方式可以分为迭代服务器和并发服务器两类。平常用C写的简单Socket客户端服务器通信,服务器每次只能处理一个客户的...

    多进程编程

    一、服务器并发访问的问题

           服务器按处理方式可以分为迭代服务器和并发服务器两类。平常用C语言编写的简单Socket客户端服务器通信,服务器每次只能处理一个客户的请求,它实现简单但效率很低,通常这种服务器被称为迭代服务器
                                  在这里插入图片描述
           但实际上,不可能让一个服务器长时间地为一个客户服务,而是需要其具有同时处理多个客户请求的能力,这种可以同时处理多个客户端请求的服务器被称为并发服务器,其效率很高却实现有些复杂。在实际应用中,并发服务器应用的最广泛。
           linux下有3种实现并发服务器的方式:多进程并发服务器,多线程并发服务器,IO多路复用。先来看多进程并发服务器的实现

    二、多进程相关简介

    1、什么是进程?

           很多人认为可执行的程序就是进程,其实这个说法并不到位!进程这个概念针对的是操作系统,而不是针对用户。进程在操作系统原理是这样描述的:正在运行的程序及其占用的资源(CPU、内存、系统资源等)叫做进程。

    2、进程空间内存布局

           在深入了解多进程编程之前,我们首先要了解Linux下进程在运行时的内存布局。Linux 进程内存管理的对象都是虚拟内存,每个进程会有 0-4G 的虚拟内存空间,0-3G 是用户空间,用来执行用户自己的代码, 而高 1GB 的空间则是内核空间执行 Linux 系统调用,这里存放在整个内核的代码和所有的内核模块。
            Linux下一个进程在内存里有三部分的数据,即”代码段”、”堆栈段”和”数据段”。学过汇编语言的同学应该知道,CPU一般都有上述三种段寄存器,这三个部分数据构成了一个完整执行序列的必要部分。
    ① 代码段:存放程序代码的数据
    ② 堆栈段:存放子程序的返回地址、子程序的参数以及程序的局部变量和malloc()动态申请内存的地址
    ③ 数据段:存放程序的全局变量,静态变量及常量

    下图是Linux下进程的内存布局:
                                     在这里插入图片描述
           Linux 内存管理的基本思想就是只有在真正访问一个地址的时候才建立这个地址的物理映射,Linux C/C++语言的分配方式共有3 种:
          (1)从静态存储区域分配。就是数据段的内存分配,这段内存在程序编译阶段就已经分配好,在程序的整个运行期间都存在,例如全局变量、static 变量。
          (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是系统栈中分配的内存容量有限,比如大额数组就会把栈空间撑爆导致段错误。
          (3)从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc()或 new 申请任意多少的内存,程序员自己负责在何时用free ()或 delete 释放内存。此 区域内存分配称之为动态内存分配。动态内存的生存期由我们决定,使用非常灵活,但问题也最多,比如指向某个内存块的指针取值发生了变化又没有其他指针指向这块内存,这块内存就无法访问,发生内存泄露。

    3、系统调用fork()

           Linux下有两个基本的系统调用可以用于创建子进程:fork()和vfork()。英文fork是"分叉"的意思。可以这样理解:一个进程在运行中,如果调用了fork(),就产生了另一个进程,于是进程就”分叉”了!

    fork()的函数原型及返回值:

    #include <unistd.h>
    
    pid_t fork(void);
    RETURN VALUE
           On success, the PID of the child process is returned in the parent, and 0 is returned in
           the  child.   On failure, -1 is returned in the parent, no child process is created, and
           errno is set appropriately.
           /*fork()系统调用会创建一个新的进程,这时它会有两次返回。
           一次返回是给父进程,其返回值是子进程的PID(Process ID),
           第二次返回是给子进程,其返回值为0。*/
    

           系统调用fork()会创建一个新的子进程,这个子进程是父进程的一个副本。这也意味着,系统在创建新的子进程成功后,会将父进程的文本段、数据段、堆栈都复制一份给子进程,但子进程有自己独立的空间,子进程对这些内存的修改并不会影响父进程空间的相应内存。这就好比你父母之前买了一套房子。等到你结婚了,又买了一套一模一样的房子给你,然后你对这套房子怎么装修都不会影响到你父母的房子!

           另外,每个子进程只能有一个父进程,并且每个进程都可以通过调用getpid()获取自己的进程PID,也可以通过getppid()获取父进程的PID,这样在fork()时返回0给子进程是可取的。一个进程可以创建多个子进程,但对于父进程而言,他并没有API函数用来获取其子进程的进程PID,所以父进程在通过fork()创建子进程的时候,必须通过返回值的形式告诉父进程其创建的子进程PID。这也是系统调用fork()设计两次返回值的原因。

           因此在调用fork()后,需要通过其返回值来判断当前的代码是父进程还是子进程在运行,如果返回值是0说明现在是子进程在运行,如果返回值>0说明是父进程在运行,而如果返回值<0的话,说明fork()系统调用出错。出错原因大致有以下两点:
           ① 系统中已经有太多的进程
           ② 该实际用户 ID 的进程总数超过了系统限制

           关于进程的退出,我们知道在main()函数里使用return,就会调用exit()函数,从而导致进程退出。对于其他函数,在其任何位置调用exit()也会导致进程退出。因此,倘若子进程使用了return,那么子进程就会退出;同理,父进程使用了return,也会退出。这里我们需要注意的是:在编程时,在程序的任何位置调用exit()函数都会导致本进程退出;在main()函数中使用return,会导致进程退出;但在其他函数中使用return都只是令这个函数返回,而不会导致进程退出。

    下面是一个简单的程序例子来描述父进程创建子进程的过程
    示例代码如下:

    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    
    int g_var = 6;
    char g_buf[] = "A string write to stdout.\n";
    
    int main (int argc, char **argv)
    {
    		int 		var = 88;
     		pid_t 		pid;
    
     		if (write(STDOUT_FILENO, g_buf, sizeof(g_buf)-1) < 0)
    	    {
     			printf("Write string to stdout error: %s\n", strerror(errno));
    			return -1;
     		}
    
     		printf("Befor fork\n");
    
     		if ((pid = fork()) < 0)
     		{
    			printf("fork() error: %s\n", strerror(errno));
    			return -2;
     		}
    		else if ( 0 == pid)
     		{
     			printf("Child process PID[%d] running...\n", getpid());
     			g_var++;
    			var++;
    		}
     		else
     		{
     			printf("Parent process PID[%d] waiting...\n", getpid());
     			sleep(1);
     		}
    
    		printf("PID=%ld, g_var=%d, var=%d\n", (long) getpid(), g_var, var);
     		return 0;
    }
    

    GCC编译:

    gcc fork_var.c -o fork_var
    ./fork_var
    

    运行结果:

    A string write to stdout.
    Befor fork
    Parent process PID[27642] waiting...
    Child process PID[27643] running...
    PID=27643, g_var=7, var=89
    PID=27642, g_var=6, var=88
    

    程序分析:

    1. 由于子进程创建后究竟是父进程还是子进程先运行并没有规定,所以父进程在第35行调用了sleep(1)的目的是希望让子进程先运行,但这个机制并不能100%保证让子进程先执行;
    2. 程序中38行的printf()被执行了两次,这是因为fork()之后,子进程会复制父进程的代码段,这样38行的代码也被复制给子进程了。并且子进程在运行到第30行后并没有使用return或exit()函数让进程退出,所以程序会继续执行到38行至39行使用return 退出子进程;同理,父进程也是执行38行至39行才退出,因此38行的printf()分别被父子进程各执行了一次。
    3. 子进程在第29行和30行改变了这两个变量的值,这个改变只影响子进程的空间的值,并不会影响父进程的内存空间,所以子进程里g_var和var分别变成了7和89,而父进程的g_var和var都没改变;
    4、子进程继承父进程哪些东西

           我们可以从上面的例子中发现,当知道子进程从父进程那里继承了什么或未继承什么时,会有助于今后的多进程编程。下面这个列表会因为不同Unix的实现而发生变化,所以不能保证完全准确。另外,子进程得到的是这些东西的拷贝,而不是它们本身。

    由子进程自父进程继承到:

    • 进程的资格(真实(real)/有效(effective)/已保存(saved)
    • 用户号(UIDs)和组号(GIDs))
    • 环境(environment)变量
    • 堆栈
    • 内存
    • 打开文件的描述符(注意对应的文件的位置由父子进程共享, 这会引起含糊情况)
    • 执行时关闭(close-on-exec) 标志
    • 信号(signal)控制设定
    • nice值 (nice值由nice函数设定,该值表示进程的优先级, 数值越小,优先级越高)
    • 进程调度类别(scheduler class) (进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,根据进程调度类别和nice值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)
    • 进程组号
    • 对话期ID(Session ID) (进程所属的对话期 (session)ID, 一个对话期包括一个或多个进程组, 详细说明参见《APUE》 9.5节)
    • 当前工作目录
    • 根目录 (根目录不一定是“/”,它可由chroot函数改变)
    • 文件方式创建屏蔽字(file mode creation mask (umask))
    • 资源限制
    • 控制终端

    子进程所独有:

    • 进程号不同的父进程号(子进程的父进程号与父进程的父进程号不同, 父进程号可由getppid函数得到)
    • 自己的文件描述符和目录流的拷贝(目录流由opendir函数创建,因其为顺序读取,顾称“目录流”)
    • 子进程不继承父进程的进程,正文(text), 数据和其它锁定内存(memory locks) (锁定内存指被锁定的虚拟内存页,锁定后, 不允许内核将其在必要时换出(page out))
    • 在tms结构中的系统时间(tms结构可由times函数获得, 它保存四个数据用于记录进程使用中央处理器 (CPU:Central Processing Unit)的时间,包括:用户时间,系统时间, 用户各子进程合计时间,系统各子进程合计时间)
    • 资源使用(resource utilizations)设定为0
    • 阻塞信号集初始化为空集
    • 不继承由timer_create函数创建的计时器
    • 不继承异步输入和输出
    • 不继承父进程设置的锁
    5、exec*()执行另外一个程序

           在上面的例子中,我们所创建的子进程是让其继续执行父进程的文本段。但实际上,创建子进程的目的更多的是想让子进程去执行另外一个程序。这时我们会在fork()之后,紧接着调用exec*()系列函数便可让子进程去执行另外一个程序。

    exec*()系列函数原型为:

    int execl(const char *path, const char *arg, ...)
    int execv(const char *path, char *const argv[])
    int execle(const char *path, const char *arg, ..., char *const envp[])
    int execve(const char *path, char *const argv[], char *const envp[])
    int execlp(const char *file, const char *arg, ...)
    int execvp(const char *file, char *const argv[])
    

    exec*()系列函数关系:

    • l 表示以列表(list)的形式传递要执行程序的命令行参数,
    • v 表示以数组(vector)的形式传递要执行程序的命令行参数,
    • e 表示给该命令传递环境变量(environment),
    • p 表示可执行文件查找方式为文件名。

    execl()函数参数说明:

    • 第一个参数 path:表示所要执行程序的路径
    • 第二个参数 arg:表示命令及其相关选项。命令、选项、参数都用双引号(" ")扩起来,并以NULL结束

           在这么多的函数调中,这里选择一个实现(execl()函数的参数相对简单,所以使用它要多些),接下来以一个程序实例来演示它的使用。
           该程序功能为通过C程序代码获取主机IP地址,首先通过fork()创建一个子进程,然后调用execl()来执行ifconfig程序,并将标准输出重定向到文件,之后父进程从该文件中读文件内容并作相应的字符串解析,最终获取IP地址!

    示例代码如下:

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <ctype.h>
    
    // 标准输出重定向的文件, /tmp路径是在Linux系统在内存里做的一个文件系统
    #define TMP_FILE "/tmp/.ifconfig.log"
    
    int main(int argc, char **argv)
    {
    	int 		fd;
    	int 		rv;
    	pid_t		pid;
    	FILE 	   *fp;
    	char 	   *ptr;
    	char 	   *ip_start;
    	char 	   *ip_end;
    	char 		ipaddr[16];
    	char		buf[1024];
    	
    	//父进程打开这个文件,子进程将会继承父进程打开的这个文件描述符,这样父子进程都可以通过各自的文件描述符访问同一个文件了
    	if ((fd = open(TMP_FILE, O_RDWR|O_CREAT|O_TRUNC, 0644)) < 0)
    	{
    		printf("Redirect standard output to file failure: %s\n", strerror(errno));
    		
    		return -1;
    	}
    	
    	pid = fork();		//父进程开始创建进程
    	if (pid < 0)
    	{
    		printf("fork() create child process failure: %s\n", strerror(errno));
    		
    		return -1;
    	}
    	else if (pid == 0) 	//子进程开始运行
    	{
    		printf("Child process start excute ifconfig program\n");
    		
    		//子进程会继承父进程打开的文件描述符,并将标准输出重定向到打开的文件中
    		//将ifconfig eth0命令在执行后的结果输出到文件中
    		dup2(fd, STDOUT_FILENO);
    		
    		/*
    			execl()函数让子进程开始执行带参数的ifconfig命令: ifconfig eth0
    			并且execl()会让子进程彻底丢掉父进程的文本段、数据段,
    			并加载/sbin/ifconfig这个程序的文本段、数据段然后重新建立进程内存空间。
    		*/
    		execl("/sbin/ifconfig", "ifconfig", "eth0", NULL);
    		
    		/* 
    		execl()函数成功执行后是不会返回的,因为他去执行另外一个程序了。
    		但如果execl()返回了,说明该系统调用出错了。
    		 */
    		printf("Child process excute another program, will not return here. Return here means
    				execl() error\n");
    		return -1;
    	}
    	else
    	{
    		sleep(3);		//父进程睡眠3s,保证子进程先执行
    	}
    	//子进程因为调用了execl(), 它会丢掉父进程的文本段,所以子进程不会执行到这里了。只有父进程会继续执行这后面的代码
    
    	fp = fdopen(fd, "r");		 //调用fdopen()函数将文件描述符fd转成文件流fp
    	fseek(fp, 0, SEEK_SET);		 //设置文件偏移量到文件起始处
    	while (fgets(buf, sizeof(buf), fp))	 //fgets()从文件里一下子读一行,读到文件尾则返回NULL
    	{
    		/*
    		已知包含IP地址的那一行包含有netmask关键字,如果在该行中找到该关键字就可以从这里面解析出IP地址了。
    		inet 192.168.2.17 netmask 255.255.255.0 broadcast 192.168.2.255
    		inet6 fe80::ba27:ebff:fee1:95c3 prefixlen 64 scopeid 0x20<link>
    		*/
    		if (strstr(buf, "netmask"))
    		{
    			//查找"inet"关键字,inet关键字后面跟的就是IP地址;
    			ptr = strstr(buf, "inet");
    			if (!ptr)
    			{
    				break;
    			}
    			ptr += strlen("inet");
    			//inet关键字后面是空白符,我们不确定是空格还是TAB,所以这里使用isblank()函数判断如果字符还是空白符就往后跳过;
    			while (isblank(*ptr))
    			{
    				ptr++;
    			}
    			//跳过空白符后跟着的就是IP地址的起始字符;
    			ip_start = ptr;
    			
    			//IP地址后面又是跟着空白字符,跳过所有的非空白字符,即IP地址部分:xxx.xxx.xxx.xxx
    			while(!isblank(*ptr))
    			{
    				ptr++;
    			}
    			//第一个空白字符的地址也就是IP地址终止的字符位置
    			ip_end = ptr;
    			
    			//使用memcpy()函数将IP地址拷贝到存放IP地址的buffer中,其中ip_end-ip_start就是IP地址的长度,ip_start就是IP地址的起始位置;
    			memset(ipaddr, 0, sizeof(ipaddr));
    			memcpy(ipaddr, ip_start, ip_end-ip_start);
    			break;
    		}
    	}
    	printf("Parser and get IP address: %s\n", ipaddr);
    	
    	fclose(fp);
    	unlink(TMP_FILE);
    	return 0;
    }
    

    程序执行结果:

    Child process start excute ifconfig program
    Read 0 bytes data dierectly read after child process write
    Read 496 bytes data after lseek:
    eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    		inet 192.168.2.17 netmask 255.255.255.0 broadcast 192.168.2.255
    		inet6 fe80::ba27:ebff:feb4:c096 prefixlen 64 scopeid 0x20<link>
    		ether b8:27:eb:b4:c0:96 txqueuelen 1000 (Ethernet)
    		RX packets 532532 bytes 129032905 (123.0 MiB)
    		RX errors 0 dropped 0 overruns 0 frame 0
    		TX packets 257545 bytes 45078393 (42.9 MiB)
    		TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    Parser and get IP address: 192.168.2.17
    
    6、系统调用vfork()

           vfork()是另外一个可以用来创建进程的函数,他与fork()的用法相同,也用于创建一个新进程。 但vfork()并不将父进程的地址空间完全复制给子进程。由于子进程会调用exec*()或exit(),因此就不会引用该地址空间了。不过子进程在调用exec()或exit()之前,他在父进程的空间中运行,此时如果子进程想尝试修改数据域(数据段、堆、栈)都会带来未知的结果,因为这会影响父进程空间的数据,从而可能导致父进程的执行出现异常。
           此外,vfork()会保证子进程先运行,在他调用了exec或exit()之后父进程才可能被调度运行。如果子进程依赖于父进程的进一步动作,则会导致死锁。

    vfork()函数原型:

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t vfork(void);
    
    7、wait()与waitpid()

           当一个进程正常或异常退出时,内核就会向其父进程发送SIGCHLD信号。因为子进程退出是一个异步事件,所以这种信号也是内核向父进程发送的一个异步通知。父进程可以选择忽略该信号,或者提供一个该信号发生时即将被执行的函数,父进程可以调用wait()或waitpid()用来查看子进程退出的状态。

    wait()与waitpid()函数原型:

    #include <sys/types.h>
    #include <sys/wait.h>
    
    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);
    
    RETURN VALUE
           wait(): on success, returns the process ID of the terminated  child;  on  error,  -1  is
           returned.
    
           waitpid():  on  success, returns the process ID of the child whose state has changed; 
           if WNOHANG was specified and one or more child(ren) specified by pid exist, but have 
           not yet changed state, then 0 is returned.  On error, -1 is returned.
    

           在子进程终止前,wait()使其调用者阻塞,而waitpid()有一选项可使调用者不用阻塞。 waitpid()并不等待在其调用的之后的第一个终止进程,他有若干个选项,可以控制他所等待的进程。 如果一个已经终止、但其父进程尚未对其调用wait()进行处理(获取终止子进程的有关信息如CPU时间片、释放它锁占用的资源如文件描述符等)的进程被称僵死进程(zombie)。如果子进程已经终止,并且是一个僵死进程,则wait()立即返回该子进程的状态。所以我们在多进程编程时,最好调用wait()或waitpid()来解决僵死进程的问题。
           此外,如果父进程在子进程退出之前退出了,这时候子进程就变成了孤儿进程。当然每一个进程都应该有一个独一无二的父进程,init进程就是这样的一个“慈父”,Linux内核中所有的子进程在变成孤儿进程之后都会被init进程“领养”,这也意味着孤儿进程的父进程最终会变成init进程。

    三、多进程编程改写服务器程序

    1、多进程并发服务器

                         在这里插入图片描述

    2、服务器端

    代码如下:

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <getopt.h>
    #include <ctype.h>
    
    void print_usage(char *progname)
    {
    	printf("%s usage: \n", progname);
    	printf("-p(--port): sepcify server listen port.\n");
    	printf("-h(--Help): print this help information.\n");
    
    	return ;
    }
    
    int main(int argc, char **argv)
    {
    	int 					sockfd = -1;
    	int 					rv = -1;
    	int 					port = 0;
    	int						clifd;
    	int 					ch;
    	int 					on = 1;
    	struct sockaddr_in 		servaddr;
    	struct sockaddr_in 		cliaddr;
    	socklen_t				len;
    	pid_t 					pid;
    	struct option opts[] = {
    			{"port", required_argument, NULL, 'p'},
    			{"help", no_argument, NULL, 'h'},
    			{NULL, 0, NULL, 0}
    			};
    			
    	while ((ch=getopt_long(argc, argv, "p:h", opts, NULL)) != -1)
    	{
    		switch (ch)
    		{
    			case 'p':
    					port=atoi(optarg);
    					break;
    			case 'h':
    					print_usage(argv[0]);
    					break;
    		}
    	}
    	
    	if (!port)
    	{
    		print_usage(argv[0]);
    		return 0;
    	}
    	
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);
    	if(sockfd < 0)
    	{
    		printf("Create socket failure: %s\n", strerror(errno));
    		return -1;
    	}
    	printf("Create socket[%d] successfully!\n", sockfd);
    	
    	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    	
    	memset(&servaddr, 0, sizeof(servaddr));
    	servaddr.sin_family=AF_INET;
    	servaddr.sin_port = htons(port);
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* listen all the IP address on this host */
    	
    	rv = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    	if (rv < 0)
    	{
    		printf("Socket[%d] bind on port[%d] failure: %s\n", sockfd, port, strerror(errno));
    		return -2;
    	}
    	
    	listen(sockfd, 13);
    	printf("Start to listen on port [%d]\n", port);
    	
    	while(1)
    	{
    		printf("Start accept new client incoming...\n");
    		
    		clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
    		if (clifd < 0)
    		{
    			printf("Accept new client failure: %s\n", strerror(errno));
    			continue;
    		}	
    		printf("Accept new client[%s:%d] successfully\n", inet_ntoa(cliaddr.sin_addr),
    				ntohs(cliaddr.sin_port));
    				
    		pid = fork();
    		if (pid < 0)
    		{
    			printf("fork() create child process failure: %s\n", strerror(errno));
    			close(clifd);
    			continue;
    		}
    		else if( pid > 0 )
    		{
    			/* Parent process close client fd and goes to accept new socket client again */
    			close(clifd);
    			continue;
    		}
    		else  //0 == pid
    		{
    			int  	i;
    			char 	buf[1024];
    
    			printf("Child process start to commuicate with socket client...\n");
    			
    			close(sockfd); /* Child process close the listen socket fd */
    			while (1)
    			{
    				memset(buf, 0, sizeof(buf));
    				
    				rv = read(clifd, buf, sizeof(buf));
    				if (rv < 0)
    				{
    					printf("Read data from client sockfd[%d] failure: %s\n", clifd,
    					strerror(errno));
    					
    					close(clifd);
    					exit(0);
    				}
    				else if (rv == 0)
    				{
    					printf("Socket[%d] get disconnected\n", clifd);
    					
    					close(clifd);
    					exit(0);
    				}
    				else //rv > 0 
    				{
    					printf("Read %d bytes data from Server: %s\n", rv, buf);
    				}
    				
    				/* convert letter from lowercase to uppercase */
    				for (i = 0; i < rv; i++)
    				{
    					buf[i] = toupper(buf[i]); //转变成大写
    				}
    				
    				rv = write(clifd, buf, rv);
    				if (rv < 0)
    				{
    					printf("Write to client by sockfd[%d] failure: %s\n", clifd,
    					strerror(errno));
    					
    					close(clifd);
    					exit(0);
    				}
    			} /* Child process loop */
    		} /* Child process start*/
    	}
    	
    	close(sockfd);
    	return 0;
    }
    

    程序分析:

    • 父进程accept()接收到新的连接后,就调用fork()系统调用来创建子进程来处理与客户端的通信。子进程会继承父进程处于listen状态的socket文件描述符(sockfd),也会继承父进程accept()返回的客户端socket文件描述符(clifd),但子进程只处理与客户端的通信,因此将父进程的listen的文件描述符sockfd关闭;同样父进程只处理监听的事件,所以将clifd关闭。
    • 父子进程同时运行完成不同的任务,子进程只负责跟已经建立的客户端通信,而父进程只用来监听到来的socket客户端连接。所以当有新的客户端到来时,父进程就有机会来处理新的客户连接请求了,每来一个客户端都会创建一个子进程为其服务。
    • 子进程使用while(1)循环让自己一直执行,并通过toupper()将客户端发过来的小写字母改成大写字母后再传回去。只有当读写socket出错或客户端主动断开时,子进程才退出,并在退出之前调用close()关闭相应的套接字。因为是在main()函数中,所以我们可以使用return或exit()退出进程,但注意不能使用break跳出。

           接下来在windows下使用TCP socket测试工具连接并测试服务器的执行情况,我们可以发现服务器可以同时处理多个客户端的连接请求和通信,并在客户端断开时子进程退出,从而实现了服务器并发访问。

    四、系统限制

           使用多进程确实可以实现多个客户端的并发,但一个服务器并不能给无限多个客户端提供服务!在Linux下每种资源都有相关的软硬限制,譬如单个用户最多能创建的子进程个数有限制,同样一个进程最多能打开的文件描述符也有相应的限制值,这些会限制服务器能够提供并发访问的客户端的数量。 在Linux系统下,我们可以使用下面两个函数来获取和设置这些限制:

    #include <sys/resource.h>
    
    int getrlimit(int resource, struct rlimit *rlim);
    int setrlimit(int resource, const struct rlimit *rlim);
    

    参数 resource说明:

    • RLIMIT_AS 进程的最大虚内存空间,字节为单位。
    • RLIMIT_CORE 内核转存文件的最大长度。
    • RLIMIT_CPU 最大允许的CPU使用时间,秒为单位。当进程达到软限制,内核将给其发送SIGXCPU信号,这一信号的默认行为是终止进程的执行。
    • RLIMIT_DATA 进程数据段的最大值。
    • RLIMIT_FSIZE 进程可建立的文件的最大长度。如果进程试图超出这一限制时,核心会给其发送SIGXFSZ信号,默认情况下将终止进程的执行。
    • RLIMIT_LOCKS 进程可建立的锁和租赁的最大值。
    • RLIMIT_MEMLOCK 进程可锁定在内存中的最大数据量,字节为单位。
    • RLIMIT_MSGQUEUE 进程可为POSIX消息队列分配的最大字节数。
    • RLIMIT_NICE 进程可通过setpriority() 或 nice()调用设置的最大完美值。
    • RLIMIT_NOFILE 指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
    • RLIMIT_NPROC 用户可拥有的最大进程数。
    • RLIMIT_RTPRIO 进程可通过sched_setscheduler 和 sched_setparam设置的最大实时优先级。
    • RLIMIT_SIGPENDING 用户可拥有的最大挂起信号数。
    • RLIMIT_STACK 最大的进程堆栈,以字节为单位。

    参数rlim说明:描述资源软硬限制的结构体

    struct rlimit 
    {
    		rlim_t rlim_cur;
    		rlim_t rlim_max;
    };
    

    下面我们用一个例程介绍一下这两个函数的使用方法。
    代码如下:

    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    #include <sys/resource.h>
    
    void print_limits(char* name, int resource)
    {
    	struct rlimit 		limit;
    	
    	if (getrlimit(resource, &limit) < 0)
    	{
    		printf("getrlimit for %s failure: %s\n", strerror(errno));
    		return ;
    	}
    	printf("%-15s ",name);
    	
    	if	(limit.rlim_cur == RLIM_INFINITY)
    	{
    		printf("(infinite) ");
    	}
    	else
    	{
    		printf("%-15ld",limit.rlim_cur);
    	}
    	
    	if (limit.rlim_max == RLIM_INFINITY)
    	{
    		printf("(infinite) ");
    	}
    	else
    	{
    		printf("%-15ld",limit.rlim_max);
    	}
    	
    	printf("\n");
    }
    
    int main(void)
    {
    	struct rlimit limit = {0};
    	
    	print_limits("RLIMIT_NPROC", RLIMIT_NPROC);
    	print_limits("RLIMIT_DATA", RLIMIT_DATA);
    	print_limits("RLIMIT_STACK", RLIMIT_STACK);
    	print_limits("RLIMIT_NOFILE", RLIMIT_NOFILE);
    	printf("\nAfter set RLIMIT_NOFILE:\n");
    	
    	getrlimit(RLIMIT_NOFILE, &limit );
    	limit.rlim_cur = limit.rlim_max;
    	setrlimit(RLIMIT_NOFILE, &limit );
    	print_limits("RLIMIT_NOFILE", RLIMIT_NOFILE);
    	
    	return 0;
    }
    

    程序运行结果如下:

    RLIMIT_NPROC 7345 7345
    RLIMIT_DATA (infinite) (infinite)
    RLIMIT_STACK 8388608 (infinite)
    RLIMIT_NOFILE 1024 1048576
    
    After set RLIMIT_NOFILE:
    RLIMIT_NOFILE 1048576 1048576
    

           由上所知,一个服务器程序抛开硬件(CPU、内存、带宽)限制以外,还会受到Linux系统的资源限制。所以,如果我们想要增加Linux服务器并发访问的客户端数量,则需要在服务器程序里通过调用setrlimit()函数来修改这些限制。

    展开全文
  • Linux -- 多进程编程之 - 基础实现、孤儿进程

    千次阅读 多人点赞 2021-10-24 20:26:21
    在Linux 中创建一个新进程的方法是使用 fork()函数。

    一、进程的创建

      在Linux中创建一个新进程的方法是使用 fork() 函数。

      fork()函数用于从已存在的进程中创建一个新进程。新进程称为子进程,而原进程称为父进程

      使用fork()函数得到的子进程父进程的一个复制品,它从父进程处继承了整个进程的 地址空间,包括进程上下文代码段进程堆栈内存信息打开的文件描述符信号处理函数进程优先级进程组号当前工作目录根目录资源限制控制终端等。

      子进程所独有的只有它的进程号资源使用计时器等。

      因为子进程几乎是父进程的完全复制,所以父子两个进程会运行同一个程序。因此需要用一种方式来区分它们,并使它们照此运行,否则,这两个进程只能做相同的事。

      父子进程一个很重要的区别是:fork()返回值不同

      父进程中的返回值是子进程的进程号,而子进程中返回0。可以通过返回值来判定该进程是父进程还是子进程

    注意:
      子进程没有执行fork()函数,而是从fork()函数调用的下一条语句开始执行。PID不同

      1、fork()函数的原型如下所示。

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t fork(void);
    

    功能:
      以精确复制父进程的方式,创建新的子进程
    返回:
      成功:父进程返回子进程PID, 子进程返回0
      失败:父进程返回-1,没有子进程

      2、获取PID以及父进程PID的函数的原型如下所示。

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t getpid(void);
    	功能:获取当前进程的PID
    pid_t getppid(void);
    	功能:获取当前进程父进程的PID
    

      3、进程创建的程序示例如下所示。

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
        int x = 0;
        /* 定义进程PID记录的变量 */
        pid_t pid = 0;
        /* 输出调试语句 */
        printf("fork() function test!\n");
    
        /* 调用fork()函数创建进程 */
        pid = fork();
        /* 函数返回值判断 */
        if (pid == -1) /* 返回错误 */
        {
            perror("fork error");
            return -1;
        }
        else if (pid == 0) /* 子进程 */
        {
            x = 10;
            printf("I'm child, pid = %d, &x = %p, x = %d\n", pid, &x, x);
            /* 子进程 */
            printf("my PID = %d, my parent PID = %d\n", getpid(), getppid());
        }
        else /* 父进程 */
        {
            sleep(1); /* 延时片刻,保证子进程先运行 */
            printf("I'm parent, pid = %d, &x = %p, x = %d\n", pid, &x, x);
            /* 父进程 */
            printf("return PID = %d, parent pid = %d\n", pid, getpid());
        }
    
        sleep(1);
    
        return 0;
    }
    
    

      编译上述程序并进程运行之后,显示效果下图1.1所示。

    图1.1 测试程序运行效果

      关于进程的退出操作,可以参考博文:Linux – exit()函数、_exit()函数、return的说明与使用

    二、孤儿进程产生与处理

      孤儿进程:子进程本身处于运行状态,但是其父进程提前结束,此子进程被称为孤儿进程。

      孤儿进程 将被 init 进程(进程号为 1 )所收养,并由 init 进程对它们完成状态收集工作,因此孤儿进程并不会有什么危害。

    孤儿进程会被1号进程收养,并最终由1号进程回收。不论子进程处于 运行状态(R)、休眠状态(S)、僵死状态(Z)、停止状态(T)都可被收养。

      下面用一个简单的程序显示一下孤儿进程的状态变化。

    #include <stdio.h>
    #include <stdlib.h> /* exit    */
    #include <unistd.h> /* getpid等 */
    
    int main(int argc, const char *argv[])
    {
        /* 定义进程PID记录的变量 */
        pid_t pid = 0;
    
        /* 输出调试语句 */
        printf("Orphan process test!\n");
    
        /* 调用fork()函数创建进程 */
        pid = fork();
        /* 函数返回值判断 */
        if (pid == -1) /* 返回错误 */
        {
            perror("fork error");
            return -1;
        }
        else if (pid == 0) /* 子进程 */
        {
            unsigned long n = 0, m = 0;
            /* 子进程 */
            printf("I'm child, my PID = %d, my parent PID = %d\n", getpid(), getppid());
    
            /* 子进程延时片刻,保持子进程在父进程后面结束 */
            sleep(10);
    
            /* 调用函数退出子进程 */
            exit(EXIT_SUCCESS);
        }
        else /* 父进程 */
        {
            /* 延时片刻,保证子进程先运行 */
            usleep(20);
    
            /* 父进程延时片刻 */
            printf("Parent sleep(5) ...\n");
            sleep(5);
    
            printf("I'm parent, return PID = %d, my pid = %d\n", pid, getpid());
            /* 调用函数退出父进程 */
            exit(EXIT_SUCCESS);
        }
    
        return 0;
    }
    
    

      编译上述程序并进程运行之后,显示效果下图2.1所示。

    图2.1 测试程序运行效果

      此时在另外一个终端可以查看当前进程的状态。使用指令ps -ajx 查看,显示效果下图2.2所示。

    图2.2 测试程序运行效果

      由上图可以看出来,在父进程退出之后,子进程的 PPID 变为了 1,也就是其父进程变为了 PPID 变为了 init 进程。

      
      好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙点个赞,当然 关注一波 那就更好了,就到这儿了,么么哒(*  ̄3)(ε ̄ *)。

    上一篇:Linux – 多任务机制(任务、进程、线程)介绍
    下一篇:Linux – 多进程编程之 - 僵尸进程

    展开全文
  • 写一个小程序在后台记录每个进程的CPU使用情况,揪出锁屏后占用CPU的进程,于是自己写了一个C++类CPUusage,方便地监视不同进程的CPU占用情况。本人编程还只是个新手,如有问题请多多指教
  • Windows进程编程

    2013-12-06 22:40:15
    (1)了解线程的概念 (2)了解进程的状态 (3)学习在程序中创建进程的方法 (4)学习在程序中结束进程的方法 (5)学习在程序中获取进程信息的方法 (6)学习进程间通信的方法
  • 主要介绍了Android编程获取sdcard卡信息的方法,可实现获取sdcard总容量、剩余容量等功能,涉及Android针对sdcard进程操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
  • Linux -- 多进程编程之 - 守护进程

    千次阅读 多人点赞 2021-11-04 22:49:19
    守护进程是一个生存期较长的进程,他常常在系统引导装入是启动,仅仅在系统关闭的才终止。也就是通常所说的 Daemon 进程,是 Linux 中的后台服务进程,通常独立于控制... Linux 中很多系统服务都是通过守护进程实现的。

    一、守护进程概述

      守护进程是一个生存期较长的进程,他常常在系统引导装入是启动,仅仅在系统关闭的才终止。也就是通常所说的 Daemon 进程,是 Linux 中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。 Linux 中很多系统服务都是通过守护进程实现的。

      在Linux中,可以根据指令 ps 命令打印进程的状态,在终端输入指令ps -ajx 可以查看当前进程的状态,如下所示(已删除部分内容)。

    ubuntu@songshuai:~$ ps -ajx 
     PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        0     1     1     1 ?           -1 Ss       0   0:03 /sbin/init splash
        0     2     0     0 ?           -1 S        0   0:00 [kthreadd]
        2     3     0     0 ?           -1 I        0   0:00 [kworker/0:0]
        2     4     0     0 ?           -1 I<       0   0:00 [kworker/0:0H]
        2     5     0     0 ?           -1 I        0   0:00 [kworker/u128:0]
        2     6     0     0 ?           -1 I<       0   0:00 [mm_percpu_wq]
        2     7     0     0 ?           -1 S        0   0:00 [ksoftirqd/0]
        2     8     0     0 ?           -1 I        0   0:00 [rcu_sched]
        2     9     0     0 ?           -1 I        0   0:00 [rcu_bh]
        1  1215  1215  1215 ?           -1 SLsl     0   0:00 /usr/sbin/lightdm
        1  1782  1782  1782 tty1      1782 Ss+      0   0:00 /sbin/agetty --noclear tty1 linux
     1763  1864  1863  1863 ?           -1 S     1000   0:00 upstart-udev-bridge --daemon --user
     1763  1874  1874  1874 ?           -1 Rs    1000   0:00 dbus-daemon --fork --session --address=unix:abstract=/tmp/dbus-den4cotw4
     1763  1886  1886  1886 ?           -1 Ss    1000   0:00 /usr/lib/x86_64-linux-gnu/hud/window-stack-bridge
     1763  1911  1910  1910 ?           -1 S     1000   0:00 upstart-dbus-bridge --daemon --system --user --bus-name system
     1763  1913  1912  1912 ?           -1 S     1000   0:00 upstart-dbus-bridge --daemon --session --user --bus-name session
     1763  1917  1916  1916 ?           -1 S     1000   0:00 upstart-file-bridge --daemon --user
     1763  1919  1918  1918 ?           -1 Sl    1000   0:04 /usr/bin/fcitx
     1763  1943  1943  1943 ?           -1 Ssl   1000   0:00 /usr/lib/x86_64-linux-gnu/bamf/bamfdaemon
     2589  2594  2594  2594 pts/2     2677 Ss    1000   0:00 bash
     2594  2677  2677  2594 pts/2     2677 R+    1000   0:00 ps -ajx
    ubuntu@songshuai:~$ 
    

      在 ps 输出示例中,内核守护进程的名字出现在方括弧中,改版本的 Linux 使用一个名为 kthreadd 的特殊内核进程来创建其他内核进程,所以 kthreadd 表现为其他内核进程的父进程。

      进程 init 的进程通常为 1,他是一个系统守护进程,除了其他工作外,主要负责各个运行层次特定的系统服务。

      在Linux 中,每一个系统与用户进行交流的界面称为 终端。每一个从此终端开始运行的进程都会依附于该终端,这个终端称为这些进程的 控制终端。当控制终端关闭时,相应的进程都会自动结束。但是守护进程却能够突破这种限制,不受终端关闭的影响。反之,如果希望某个进程不因为用户、终端或者其他的变化而受到影响,那么就必须把这个进程变成一个 守护进程

    二、守护进程创建

      创建守护进程,需要遵循特定的流程,以防产生不必要的交互过程。下面就分几个步骤来创建一个简单的守护进程。

    2.1、创建子进程,父进程退出

      由于守护进程是脱离控制终端的,因此完成第一步后子进程变成后台进程。之后的所有工作都在子进程中完成。而用户通过 shell 可以执行其他的命令,从而在形式上做到了与控制终端的脱离。

      另外一方面虽然子进程继承了父进程的进程组 ID,但获得了一个新的进程 ID,这样保证了子进程不是一个进程组的组长进程。这也是下面要进程的第三步的先决条件。

    说明:
      由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程(孤儿进程可以查看博文:Linux – 多进程编程之 - 基础实现、孤儿进程)。在 Linux 中,每当系统发现一个孤儿进程,就会自动由 1号进程(也就是 init 进程)收养它,这样,原先的子进程就会变成 init 进程的子进程了。

    2.2、在子进程中创建新会话

      这个步骤是创建守护进程中最重要的一步,在这里使用的函数是 setsid()

    2.2.1、进程组和会话期

      这里先要明确两个概念:进程组和会话期。

    进程组
      进程组是一个或多个进程的集合。进程组由进程组 ID 来唯一标识。除了进程号( PID )之外,进程组 ID 也是一个进程的必备属性。
    每个进程组都有一个组长进程,其组长进程的进程号等于进程组 ID ,且进程组 ID 不会因组长进程的退出而受到影响。

    会话期
      会话组是一个或多个进程组的集合。通常一个会话开始于用户登录,终止于用户退出;或者说开始于终端打开,结束于终端关闭。会话期的第一个进程称为会话组长。在此期间该用户运行的所有进程都属于这个会话期。

      进程组和会话期之间的关系如图2.1所示。

    图2.1 进程组和会话期之间的关系图

    2.2.2、setsid()函数说明

    1、setsid()函数原型

      setsid()函数原型如下所示(使用指令 man 2 setsid 即可显示如下代码)。

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t setsid(void);
    

    功能:
      如果调用进程不是进程组长,则 setsid() 将创建一个新会话。调用进程将成为新会话的会话组组长(即,其会话 ID 与其进程 ID 相同)。同时调用进程也将成为会话中新进程组的进程组组长(即,其进程组 ID 与其进程 ID 相同)。调用进程将是新进程组和新会话中的唯一进程。
    参数:无
    返回:
      成功:返回调用进程的(新)会话ID
      失败:返回(pid_t)-1,并设置 errno

    2、setsid()函数作用

      上面已经提到,setsid() 函数用于创建一个新的会话,并担任该会话的组长,所以调用 setsid() 有下面 3 个作用。

    1、让进程摆脱原会话的控制
    2、让进程摆脱原进程组的控制
    3、让进程摆脱原控制终端的控制

      由于在调用 fork() 函数时,子进程 全盘复制 了父进程的会话期进程组和控制终端等。所以虽然父进程退出了,但原先的 会话期进程组控制终端等并没有改变,因此,子进程并不是真正意义上的独立,而 setsid() 函数能够使进程完全独立出来,从而脱离所有其他进程的控制。

    2.3、改变当前工作目录

      使用 fork() 函数创建的子进程是完全继承了父进程的当前工作目录,所以从父进程继承过来的当前工作目录可能是一个挂载的文件系统中。因为守护进程有一般情况是在系统在引导之前是一直从在的,所以在进程工作的过程中当前目录所在的文件系统(比如“/mnt/usb” 等)是不能卸载的。

      因此,一般的做法是将根目录作为守护进程的当前工作目录,这样就可以避免上述问题。当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如“/tmp”

      改变工作目录的函数是 chdir() 函数,其函数原型如下所示。

    #include <unistd.h>
    
    int chdir(const char *path);
    

    功能:
      改变调用者的工作目录
    参数:
      path:新的工作目录的路径
    返回:
      成功:返回0
      失败:返回-1,同时设置errno

    2.4、重设文件权限掩码

      文件权限掩码(通常用八进制表示)的作用是屏蔽文件权限中的对应位。例如,如果文件权限拖码是050,它表示屏蔽了文件组拥有者的可读与可执行权限。由于使用 fork() 函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了一定的影响。如果守护进程需要创建文件,那么他可能需要设置特定的权限。因此,把文件权限掩码设置为一个已知的值(通常设置为0),可以增强该守护进程的灵活性。

      设置文件权限掩码的函数是 umask()。在这里,通常的使用方法为 umask(0)。其函数原型如下所示。

    #include <sys/types.h>
    #include <sys/stat.h>
    
    mode_t umask(mode_t mask);
    

    功能:
      umask() 将调用进程的文件模式创建掩码( umask)设置为 mask & 0777(即仅使用掩码的文件权限位)。
    参数:
      mask:要设置的权限值,用八进制表示
    返回:
      此系统调用始终成功,并返回掩码的上一个值。

    2.5、关闭不需要的文件描述符

      同样地,用 fork() 函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程访问,但它们一样占用系统资源,而且还可能导致所在的文件系统无法被卸载。

      特别是守护进程和终端无关,所以指向终端设备的标准输入、标准输出和标准错误流等已经不再使用,应当被关闭。

      可以使用函数 getdtablesize() 来获取当前金成文件描述符表的大小,并通过使用 close() 来依次关闭。

    函数原型如下。

    #include <unistd.h>
    int getdtablesize(void);
    

    getdtablesize()函数返回进程可以打开的最大文件数,比文件描述符的最大可能值多一个。

    #include <unistd.h>
    int close(int fd);
    

    close() 用于关闭文件描述符,关闭成功则返回 0,失败则返回 -1 并设置 errno

      所以关闭文件描述符的代码可以如下写法。

    int num = getdtablesize(); // 获取当前进程文件描述符表大小
    
    for (int i = 0; i < num; i++)  
    {
        close (i);
    }
    

    2.6、某些特殊的守护进程打开/dev/null

      某些特殊的守护进程打开/dev/null,使其具有文件描述符0、1、2,这样任何一个试图读标准输入、标准输出、标准出错时都不会有任何效果,这样符合了守护进程不与终端设备相关联的属性。

      综上所述,一个守护进程就可以创建成功了,所以创建守护进程的流程可以总结为如图2.2所示。

    图2.2 创建守护进程流程图

    三、守护进程代码示例

      用下面的一个简单的程序,用来示例守护进程的完整的创建过程。守护进程的主要工作则是每隔一定时间向日志文件“/daemon.log”文件中写入内容。

    #include <fcntl.h>     // for O_APPEND ..
    #include <stdio.h>     // for perror ..
    #include <stdlib.h>    // for exit ..
    #include <string.h>    // for strlen
    #include <sys/stat.h>  // for umask
    #include <sys/types.h> // for setsid
    #include <unistd.h>    // for setsid
    
    int main(int argc, const char *argv[])
    {
        pid_t pid = 0;
        int i = 0;
        char *filePath = "daemon.log";
    
        /* 第一步:创建子进程,父进程退出 */
        pid = fork();
        if (pid == -1) /* fork出错 */
        {
            perror("fork error");
            exit(EXIT_FAILURE);
        }
        else if (pid == 0) /* 子进程 */
        {
            pid_t temp_pid = 0;
            /* 周期计数的变量 */
            int cycleCnt = 0;
            /* 第二步:创建新的会话 */
            temp_pid = setsid();
            /* 第三部:改变当前的工作路径*/
            chdir("/");
            /* 第四步:改变进程本身的umask */
            umask(0);
            /* 第五步:关闭所有可能已打开的文件描述符 */
            int num = getdtablesize(); /* 获取当前进程文件描述符表大小 */
            for (i = 0; i < num; i++)
            {
                close(i);
            }
    
            /* 至此,守护进程创建完成,以下正式开始守护进程的工作 */
            /* 1、打开要操作的文件 */
            int fd = open(filePath, O_RDWR | O_CREAT | O_APPEND, 0600);
            if (fd == -1) /* open操作失败 */
            {
                perror("open error");
                exit(EXIT_FAILURE);
            }
    
            /* 2、在文件中循环写入测试数据 */
            while (1)
            {
                /* 写入内容的缓冲区定义 */
                char writeBuff[128] = {0};
                /* 周期运行计数自加 */
                cycleCnt++;
                /* 写入的数据拼接 */
                sprintf(writeBuff, "I'm Daemon Process, Running %d\n", cycleCnt);
                /* 写入到文件中 */
                write(fd, writeBuff, strlen(writeBuff));
                /* 休眠片刻 */
                sleep(1);
            }
        }
        else /* 父进程 */
        {
            /* 父进程退出 */
            exit(EXIT_SUCCESS);
        }
    
        return 0;
    }
    
    

      编译并运行上述程序,运行效果及进程状态如图3.1所示。

    图3.1 运行效果及进程状态效果图

      需要注意的是,因为守护进程的工作目录已经修改为“\”,并且程序中需要在根目录中创建文件并写入,所以需要root权限,运行程序使用 sudo 命令(下同)。

      此时查看程序记录的文件,使用指令sudo cat daemon.log 即可显示,文件内容如图3.2所示。

    图3.2 文件内容显示效果图

      至此为止,守护进程相关的主要知识基本上总结的差不多了。基于守护进程的特征,那么在记录守护进程的异常处理方面也需要特殊的处理,那么将会下一篇进程守护进程的出错的处理。

      好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙点个赞,当然 关注一波 那就更好了,就到这儿了,么么哒(*  ̄3)(ε ̄ *)。

    上一篇:Linux – 多进程编程之 - 僵尸进程
    下一篇:Linux – 多进程编程之 - 守护进程的出错处理

    展开全文
  • Linux C/C++编程之(十六)进程进程控制

    千次阅读 多人点赞 2020-07-07 11:47:43
    )则子进程不会打印begin… 2)getpid与getppid 函数作用:获取进程id 头文件 返回值: getpid 获得当前进程的pid,getppid获取当前进程父进程的pid。 (1)查看进程信息: init进程是所有进程的祖先。 ps命令: ps ...
  • c++ 多线程和多进程编程

    千次阅读 2021-08-28 22:54:44
    1、概念 (1)线程 执行处理器调度的基本单位。程序执行过程中的最小单元,由线程ID、程序...进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程...
  • Linux多进程编程(典藏、含代码)

    千次阅读 多人点赞 2020-02-03 21:44:40
    1.2、多进程(任务)并行的实现 1.3、重要指令 1.4、父子进程进程ID 二、多进程编程 2.1创建子进程 (fork/vfork 叉子) 2.1.1 fork 2.1.2vfork 2.2进程结束 2.2.1正常退出 2.2.2异常退出 2.3等待进程结束并...
  • 通过使用Socket编程获取可用进程实现
  • 可以每个接触到多进程编程的人在遇到fork()函数的时候都会由一些疑惑,它怎么能返回两次?而且返回值不同。对于以前的认知大家都知道一个函数只能返回依次啊。  呵呵,这是fork的神奇所在,它为什么这么神奇?它...
  • c++多进程编程

    万次阅读 2020-04-16 10:12:52
    c++多进程编程 介绍 进程进程是一个正在执行的程序,是向CPU申请资源的,进程之间数据相互独立,一个进程至少有一个线程。 线程:线程是进程中的单一的顺序控制流程也可以叫做最小控制单元,线程是进程中执行单元...
  • QT多进程编程

    千次阅读 2019-11-08 17:05:24
    单个进程 使用QT中的QProcess类来启动一个外部·程序并与其进行通信。 要启动一个进程,可以使用start()函数,然后传递给该函数程序名称和运行这个程序所要使用的命令行参数。 执行完start函数,QProcess进入...
  • 网络编程进程

    千次阅读 2019-10-22 16:53:54
    进程服务器端 首先博主在这里先告诉大家博主学习的书籍是由 [韩]韩圣雨 著 金国哲 译 的 TCP/IP网络编程,把网络变成写的通俗易懂。 下面是百度云链接,大家感兴趣的也可以看一看 链接:...
  • 操作系统进程调度算法 c语言实现

    热门讨论 2011-04-07 08:59:30
    实现进程调度算法,具有后备序列的调度 题目:设计一个有 N个进程共行的进程调度程序。 进程调度算法:采用最高优先数优先的调度算法(即把处理机分配给优先数最高的进程)和先来先服务算法。 每个进程有一个进程...
  • 本文实例讲述了Android编程实现任务管理器的方法。分享给大家供大家参考,具体如下: 任务管理器可以实现的功能有: 1.查看当前系统下运行的所有的进程 2.可以查看每个进程进程号、版本号以及内存占用情况 3.杀死...
  • Linux 多进程编程

    千次阅读 2022-01-28 21:50:42
    进程简介 进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 注意:创建一个进程时,同一个程序会从进程创建的位置执行两次。 相关api pid_t fork() //#include <unistd.h> 创建进程 void exit...
  • 【Linux系统编程进程概述和进程

    千次阅读 2019-11-03 21:31:48
    进程概述02. 进程状态03. 进程控制块04. 进程号05. 进程号相关函数06. 案例实战07. 附录 01. 进程概述 我们平时写的 C 语言代码,通过编译器编译,最终它会成为一个可执行程序,当这个可执行程序运行起来后(没有...
  • Qt:Windows编程—Qt实现进程管理

    千次阅读 2019-01-20 23:04:44
    利用相关的API使用Qt写界面实现了一个简单的进程管理。主要用到 进程的创建、进程的枚举、线程的枚举、DLL的枚举、进程提权等功能。相关API的介绍可以看 C/C++:Windows编程—创建进程、终止进程、枚举进程、枚举...
  • Linux下用C++实现通过进程名称获取进程ID   个人分类: Linux 编程开发 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/DeliaPu/article/details/81334833 近期开发的系统功能...
  • 在虚拟机实现,A进程到B进程间采用共享内存的方式,来实现单向通信的需求。AB进程均采用C语言编程实现
  • Linux 进程编程入门

    千次阅读 2022-03-27 22:04:28
    关于进程和线程的关系,之前一口君写过这几篇文章,大家可以参考下。 本文从头带着大家一起学习Linux进程 《搞懂进程组、会话、控制终端关系,才能明白守护进程干嘛的?》 《[粉丝问答6]子进程进程的父进程关系》 ...
  • 内容索引:VB源码,系统相关,枚举窗口 VB实现枚举当前打开的所有窗口以及进程信息,选中窗口标题可显示详细信息。应该说比较详细了,希望能帮到VB的编程朋友哦!
  • Linux -- 多进程编程之 - 守护进程的出错处理

    千次阅读 多人点赞 2021-11-27 23:08:55
    因为守护进程完全脱离终端控制,所以不能像其它进程一样将错误信息输出到控制终端。所以如何处理错误消息是一个问题。在Linux系统中,一般通用的办法是使用syslog服务,将程序中出错信息输入到系统日志文件中(如"/...
  • shell脚本 实现Linux进程数量信息

    千次阅读 2018-07-22 12:06:54
    脚本如下: [root@localhost shell]# vim linuxprocess.sh  #!/bin/bash running=0 -----用于统计正在运行的程序 sleeping=0 -----用于统计正在睡眠的进程,但可...zombie=0 ------用于统计僵尸程序,进程已经终...
  • 文章目录进程间通信TCP/IPQShared Memory(共享内存)QSharedMemory数据写入共享内存读取共享内存数据实例源码获取D-BusQCOPQProcessSession Management 进程间通信 TCP/IP QShared Memory(共享内存) D-Bus QCOP...
  • Linux 系统编程 -进程概念篇

    万次阅读 多人点赞 2021-06-07 20:16:56
    Linux系统编程-进程篇冯诺依曼体系结构冯诺依曼的两个重要思想当代计算机的三级缓存操作系统操作系统的概念操作系统的组成操作系统作用Linux下的操作系统体系进程进程概念进程特性进程的组成进程与程序区别进程控制...
  • 进程同步和通信 ...为了实现对并发进程的有效管理,在多道程序系统中引入了同步机制,常见的同步机制有:硬件同步机制、信号量机制、管程机制等,利用它们确保程序执行的可再现性; 进程同步的基本概念 ...
  • 获取进程

    2015-06-24 10:27:59
    用C语言实现获取linux进程中的进程号、父进程号、组进程

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 301,532
精华内容 120,612
关键字:

如何编程实现获得进程的详细信息