Linux-C 进程

Linux-C 进程

一、简述

     记--进程的概念,简单的创建并使用进程。进程是资源的最小单位。所有的进程都是1号进程的子进程,一个进程至少有一个线程,也可以有多个线程。

     每个进程一般都拥有4G的虚拟内存(真实并没有)。创建一个子进程,子进程也拥有独立的4G虚拟内存,并且复制父进程的运行状态(代码都是一样的,并且代码运行到哪都一样)

     子进程退出后一般进入“僵死态”,由其父进程回收资源,若是其父进程已经结束,则由父进程的父进程回收,以此类推。

二、创建进程

        2.1 fork()函数

功能 创建子进程
头文件  #include <sys/types.h>
 #include <unistd.h>
函数原型 pid_t fork(void);
返回值 成功时,子进程的PID将在父进程中返回,并在子进程中返回0。 失败时,在父项中返回-1,不创建子进程,并正确设置errno。
备注 子进程资源、代码、内存状况都与父进程的一样,互不干扰。子进程就从fork()之后的代码执行。

       子进程资源、代码、内存状况都与父进程的一样,子进程从fork()函数之后的代码执行。

      

      

      虽然父进程与子进程代码相同,但是fork()的返回值不同,在父进程中返回值是子进程的PID,而在子进程中返回值是0。

      测试代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
	printf("main\n");
	pid_t pid = fork();
	
	printf("pid=%d\n", pid);
	return 0;
}

      

      

      通过fork的返回值来区分父进程和子进程,从而执行不同的动作

      

     

三、退出进程

        3.1 exit()函数

功能 终止一个进程
头文件 #include <stdlib.h>
函数原型 void exit(int status);
参数 退出状态,常用宏EXIT_SUCCESS表示成功, EXIT_FAILURE表示失败
返回值
备注

1、status&0377的值(其实就是status的低8位)返回给父进程,在父进程可用wait()来获取。(0377-->011 111 111)

2、在退出进程前会先执行atexit或者是on_exit所登记的退出处理函数
3、清理IO缓冲区,假如退出程序之前输出缓冲区有数据则会输出。
4、 _exit:直接退出进程,不会进行多余的操作(例如不会输出缓冲区的内容)

      父进程等待子进程结束,并获取子进程的结束状态。使用宏WEXITSTATUS来获取。

     

    在退出进程前会先执行atexit或者是on_exit所登记的退出处理函数,

    atexit()函数

功能 注册一个函数,在程序退出前调用
头文件 #include <stdlib.h>
函数原型 int atexit(void (*function)(void));
参数 一个无返回值,无参数的函数指针
返回值 成功返回0,失败返回非0值

on_exit()函数

功能 注册一个函数,在程序退出前调用
头文件 #include <stdlib.h>
函数原型 int on_exit(void (*function)(int , void *), void *arg);
参数

function是一个待参数的函数指针

arg是function指向函数的参数,以字符串形式

返回值 成功返回0,失败返回非0值
备注 func(int status, void *);一个是退出状态,一个是参数地址

测试代码

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

void func(int status, void *arg)
{
	printf("status=%d, arg=%s", status, (char *)arg);
}

int main(int argc, char *argv[])
{
	printf("main\n");

	pid_t pid = fork();

	on_exit(func," this is on_exit call\n");
	if(pid==0)
	{
		printf("this is child process!\n");
		exit(5);
	}
	

	printf("this is parent process!\n");
	

	return 0;
}

    

 exit退出会清理IO缓冲区,_exit()直接退出,不清理缓冲区

  

  

三、等待进程

      wait()函数

功能 等待进程改变状态
头文件 #include <sys/types.h>
#include <sys/wait.h>
函数原型

pid_t wait(int *wstatus);

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

参数 pid:是要等待的子进程的PID >0:等待进程号为pid的子进程
 =0:等待调用者所在进程组的  任一子进程
  -1:等待任一子进程退出(跟wait一样),可以不同进程组
小于-1:等待组ID的绝对值为pid的进程组 中的任一子进程
wstatus    是子进程的状态 比如子进程的退出状态
options:附加选项 0:正常引用该函数

WCONTINUED:报告任一从暂停态出来且从未报告过的子进程的状态

WNOHANG:非阻塞等待

WUNTRACED:报告任一当前处于暂停态且从未报告过的子进程的状态

返回值 成功返回退出的子进程的pid,失败返回-1,返回0则是设置了非阻塞等待,其没有出错但是又没有子进程退出
备注

如果不需要获取子进程的退出状态,stat_loc 可以设置为 NULL

WEXITSTATUS(status);返回子进程的退出状态。 这包括子项在exit(3)或_exit(2)的调用中指定的status参数的最低有效8位,或者作为main()中return语句的参数。 仅当WIFEXITED返回true时才应使用此宏。

四、vfork()与exec()

功能 创建子进程并阻塞父进程
头文件 #include <sys/types.h>
#include <unistd.h>
函数原型 pid_t vfork(void);
返回值 成功时,子进程的PID将在父进程中返回,并在子进程中返回0。 失败时,在父项中返回-1,不创建子进程,并正确设置errno。
备注 相对比于fork有以下特点:
              1、共享父进程的资源,也就是共同操作一份虚拟内存,数据相互影响。
              2、父进程进入睡眠,一直等到子进程结束或者是调用exec系列函数。
              3、专门用来于exec系列函数配合使用。

     资源共享,内存空间一样,共同操作一份数据。父进程进入睡眠,一直等到子进程结束。

    

    vfork()与exec()系列函数进行配合使用

功能 在进程中加载新的程序文件或者脚本,覆盖原有代码,重新运行。(调用第三方程序,成功调用原来程序就不继续执行了)
头文件 #include <unistd.h>
函数原型

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 execlp(const char *file, const char *arg, ...); int execvp(const char *file, char *const argv[ ]);

int execvpe(const char *file, char *const argv[ ],char *const envp[ ]);

  path 即将被加载执行的 ELF 文件或脚本的路径
  file 即将被加载执行的 ELF 文件或脚本的名字
  arg 以列表方式罗列的 ELF 文件或脚本的参数
  argv 以数组方式组织的 ELF 文件或脚本的参数
  envp 用户自定义的环境变量数组
返回值 调用第三方程序,成功调用原来程序就不继续执行了,返回值就没用了。如果调用失败或出错就返回-1。
备注

1,函数名带字母 l 意味着其参数以列表(list)的方式提供。

2,函数名带字母 v 意味着其参数以矢量(vector)数组的方式提供。

3,函数名带字母 p 意味着会利用环境变量 PATH 来找寻指定的执行文件。

4,函数名带字母 e 意味着用户提供自定义的环境变量。

*在给execl()函数传参时,最后一个参数为NULL,以标识参数结尾。

子进程中成功调用了第三方程序,子进程后面的代码就不会执行了。如例子中的printf("in child i=%d\n", i);就不会执行。

可以使用execlp()函数调用系统命令

五、守护进程

    1、守护进程的概念

          类似于服务。通常创建的子进程受限于父进程,受限于组等。比如当某个信号是发送的该组的话,该组所有的进程都将收到影响。比如说在终端运行一个程序a,a程序会占用终端的控制资源,控制终端等程序a结束后才继续。而在控制终端下按下Ctrl+c(向子进程发出挂断信号),或者直接关掉终端,程序a都会被终端并退出。而守护进程就相当于独立出来,不再受组、父进程等影响的一个进程。例如在终端运行的一个程序,关闭终端后,守护进程不受影响,然可继续运行。

      

    2、将一个普通进程变为守护进程

          

代码:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
	pid_t pid;
	int max_fd, i;
	
	//1 忽略挂断信号
	signal(SIGHUP, SIG_IGN);
	
	//2  创建子进程,并结束父进程
	pid = fork();
	if(pid>0)
		exit(0);
	
	//3 设置新的会话
	setsid();
	
	//4 创建子进程,并结束父进程
	pid = fork();
	if(pid>0)
		exit(0);
	
	//5 设置新的组
	setpgrp();
	
	//6 关闭所有描述符,(输出信息可以记录在系统日志,/var/log/syslog)
	max_fd = sysconf(_SC_OPEN_MAX);
	for(i=0;i<max_fd;i++)
		close(i);
	//7  改变掩码
	umask(0);
	
	//8  改变工作路径
	chdir("/");
	
	pause();//例子中什么事情都不做,就暂停
	
	
	
	return 0;
}

结果:

  可以使用命令:kill -s 9 进程ID  或   killall 进程名称          来结束进程

  由于关闭了所有打开的文件描述符(包括标准输入0,标准输出1、标准出错2),所以需要输出信息的的话,可以使用系统日志文件。(待补充)

 openlog函数打开日志,syslog写入日志,closelog关闭日志。
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);

六、进程间通信

     1、管道

     2、信号

     3、套接字

     4、system V-IPC

等等。。。。。

猜你喜欢

转载自blog.csdn.net/nanfeibuyi/article/details/81872914