Linux环境:C编程多进程操作

Linux环境:C编程多进程操作

      • 进程基础知识
          • 进程标识
          • 进程运行身份
          • Linux下进程管理命令
      • 创建进程:四类函数
          • 标准C函数`system`
          • Linux C 接口`fork` 函数
          • Linux C 接口`exec` 函数族
          • 标准C函数`popen`
      • 进程控制与终止
          • 进程控制
            • 孤儿进程
            • 僵尸进程
            • 进程清理
          • 进程终止
      • 进程关系
          • 文件继承
          • 守护进程`Daemon`

进程基础知识

进程是程序与数据的结合,即运行中的程序。每个进程的所有状态信息保存在各自的进程控制块(PCB)中

进程标识

每个进程都有一个唯一的进程标识号pid,pid是一个整型变量。
同时每个进程都有 一个父进程标识号ppid来记录其父进程。
Linux系统中,init进程是所有其他进程的祖先进程,init进程没有父进程,其pid=1,ppid=0,
通过函数pid_t getpid(void) , pid_t getppid(void)可以获得当前进程的pidppid

进程运行身份

进程运行时需要对数据进行读写,而为了检查进程是否有读写文件的权限,需要通过进程的用户 ID 和组 ID来确认进程的访问权限。
进程的用户ID和用户组ID分为真实ID有效ID

  • 真实ID是启动进程的用户ID和其组ID,可以通过函数uid_t getuid(void)uid_t getgid(void)获得
  • 有效ID是内核对进程的访问权限检查时实际检查的ID,缺省状态下等同于真实ID。可以通过对可执行文件的权限设定,使得有效ID与真实ID不同。可以通过函数uid_t geteuid(void)uid_t getegid(void)获得

应用:
现有文件权限755的可执行文件1c.exe可以对root用户目录下文件进行读写,所有者为root用户。
当使用普通用户执行1c.exe时,有效用户ID为普通用户,所以没有权限访问root用户的文件。
此时可以将1c.exe文件的可执行参数改为s,使用chmod 4755 1c.exechmod u+s 1c.exe命令,之后再使用普通用户执行1c.exe时,可以正常读写root用户的文件。
这就是s参数的作用,可以使可执行文件执行时产生的进程的有效用户ID变为可执行文件的所有者,从而固定了该可执行文件对应进程的访问权限。

Linux下进程管理命令

常用进程管理命令:

命令 含义 常用参数
ps 查看系统中的进程 -e显示所有程序;-l采用详细的格式来显示程序状况;-f显示UID,PPIP,C与STIME栏位;-aux 查看%cpu(cpu 使用量) %mem(内存使用量)进程状态
top 动态显示系统中的进程 d<间隔秒数>  设置top监控程序执行状况的间隔时间,单位以秒计算 ;c  显示每个程序的完整指令,包括指令名称,路径和参数等相关信息;n<执行次数> 设置监控信息的更新次数
nice 改变程序执行优先级 -n<优先等级>-<优先等级>--adjustment=<优先等级>  设置欲执行的指令的优先权等级,等级的范围从-20-19,其中-20最高,19最低,只有系统管理者可以设置负数的等级
kill 给程序发信号。缺省信号为SIGTERM(15),可将指定程序终止 -l <信息编号>  若不加<信息编号>选项,则-l参数会列出全部的信息名称;-s <信息名称或编号>  指定要送出的信息。
bp 将程序放到后台运行 也可以通过在指令后添加&直接把任务转入后台
fg 将后台任务转到前台执行 ——

创建进程:四类函数

标准C函数system

函数原型:
int system(const char *string)
特点:

  • system 函数通过调用 shell 程序/bin/sh –c 来执行 string 所指定的命令。
  • 通过 system 创建子进程后,原进程和子进程各自运行,相互间关联较少
  • system 调用成功,将返回 0,创建进程失败返回0
  • system函数后面的参数还可以是一个可执行程序,通过加&可以使该程序后台运行
Linux C 接口fork 函数

函数原型:
pid_t fork(void)
函数原理
fork函数执行后创建一份当前进程的复制作为当前进程的子进程,子进程的进程空间与父进程一致,拥有的资源相同,只有进程号,计时器,和进程对资源的取用操作不同。通过判断fork函数的返回值可以判断是子进程还是父进程。
如果返回值是0则是子进程,大于0则是父进程,且返回值是子进程的进程号。如果返回负值则表示创建子进程失败。
示例程序:

int main()
{
	int pid = fork();
	if(pid<0){
		printf("创建子进程失败!\n");
	}
	else
	{
		if(fork==0)
		{
			printf("这是子进程,进程号为:%d\n",getpid());
		}
		else
		{
			pirntf("这是父进程,子进程进程号为:%d\n",pid);
		}
	}
	return 0;
}

执行效果如下:
在这里插入图片描述

Linux C 接口exec 函数族

函数机制:exec函数族会调用已存在的可执行程序覆盖原有进程的进程空间,相当于进程替换,进程号不变,原进程不再执行。替换失败返回-1。
常用exec函数:

  • int execl(const char *path, const char *arg, ...);
    path:完整路径,被调用函数的参数列表,NULL结尾

  • int execlp(const char *file, const char *arg, ...);
    第一个参数不用输入完整路径,给出命令名即可,系统会在环境变量PATH当中查找命令
    后面跟被调用函数的参数列表,NULL结尾

  • int execle(const char *path, const char *arg, ..., char * const envp[]);
    path:完整路径,被调用函数的参数列表,NULL结尾,然后跟环境变量字符串数组,可以把环境变量传入新进程

  • int execv(const char *path, char *const argv[]);
    path:完整路径,命令所需的参数以char *arg[]形式给出且arg最后一个元素为NULL
    示例

#include <fun.h>

int main()
{
    printf("this is father!\n");
    execl("../fork/forkc.exe","fork.exe",NULL);
    printf("this is still father!\n");
    return 0;
}

执行效果:
在这里插入图片描述
可以看到第二句printf没有执行,因为原进程已经被新进程取代。

标准C函数popen

函数原型

FILE *popen(const char *command, const char *type);

函数机制

  • command 为可执行文件的全路径和执行参数;相当于用该程序创建新进程
  • type 可选参数为”r”或”w”:
    • 如果为”w”,则 popen 返回的文件流做为新进程的标准输入流,原进程向其中写入数据,子进程可以通过标准输入流函数读数据;
    • 如果为”r”,则 popen 返回的文件流做为新进程的标准输出流,新进程可以通过stdout写数据,父进程可以通过该文件指针收到数据,从而实现了进程通信。相当于创建了无名管道。

进程控制与终止

进程控制
孤儿进程

父进程先于子进程终止,则子进程的父进程自动变为祖先进程init,由祖先进程init负责子进程终止后的清理工作,但在 init 进程清理子进程之前,会一直消耗系统的资源
样例

int main()
{
    int pid = fork();
    if(pid == 0)
    {
        sleep(10);
    }
    else{
    exit(0);
         }
    return 0;
}

僵尸进程

子进程先于父进程终止,则子进程的空间应由父进程负责清理,如果父进程不进行清理,则死掉的子进程会变为僵尸进程,占用系统资源,影响系统性能。

int main()
{
    int pid = fork();
    if(pid==0)
    {
        exit(0);
    }
    else{
        sleep(10);
    }
    return 0;
}

进程清理

父进程需要手动调用wait()waitpid() 函数来完成子进程清理工作。

  • pid_t wait(int *status);暂停父进程,随机等待一个已经退出的子进程,并返回该子进程的 pid;
  • pid_t waitpid(pid_t pid, int *status, int options);暂停父进程,等待一个指定pid的子进程结束,如果pid为-1表示等待所有进程options 用于改变 waitpid 的行为,其中最常用的是 WNOHANG,它表示无论子进程是否退出都将立即返回,不会将调用者挂起,没有退出的子进程返回0。
  • status 参数是传出参数,存放子进程的退出状态;可以通过对status执行两个宏来获取子进程的信息:WIFEXITED(status) 如果子进程正常结束,返回非 0 值,否则返回0值;WEXITSTATUS(status) 如果 WIFEXITED 非零,即子进程正常结束,它返回子进程的退出码
进程终止

正常终止:

  • main函数的自然返回:

  • 调用 exit 函数:void exit(int status); status传递进程退出的状态码,0代表正常,非0代表不正常。exit退出时会清理IO缓冲区

  • 调用_exit 函数:void _exit(int status);``status传递进程退出的状态码,0代表正常,非0代表不正常。_exit退出时不清理IO缓冲区

非正常终止:

  • 调用 abort 函数
  • 接收到能导致进程终止的信号 ctrl+c SIGINT 或ctrl+\ SIGQUIT

进程关系

文件继承

通过fork创建子进程,子进程复制父进程的进程空间,继承父进程打开的文件,相当于该文件的引用计数加1,当父进程关闭文件时,子进程不受影响。

守护进程Daemon

Daemon()程序是一直运行的服务端程序,又称为守护进程。通常在系统后台运行,没有控制终端,不与前台交互,Daemon程序一般作为系统服务使用。Daemon是长时间运行的进程,通常在系统启动后就运行,在系统关闭时才结束,Linux的多大服务器就是用守护进程实现的,例如ftp服务器,ssh服务器,wed服务器等我们也把运行的Daemon程序称作守护进程。


特点如下:

  • 不依赖于终端,后台运行,执行过程中信息不在终端显示
  • 与其运行前的环境隔离开来

如何将一个进程启动为守护进程:

  1. 调用fork创建子进程,父进程退出,使得子进程由init进程收养
  2. 在子进程中创建新会话,从而摆脱原会话终端对子进程的控制
  3. 改变当前目录为根目录:使用 fork 函数创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件是不能卸载的,这对以后的使用会造成很多的不便。利用 chdir("/");把当前工作目录切换到根目录。
  4. 重设文件权限掩码:umask(0);将文件权限掩码设为 0,Deamon 创建文件不会有太大麻烦
  5. 关闭所有不需要的文件描述符:新进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,而它们一直消耗系统资源。另外守护进程已经与所属的终端失去联系,那么从终端输入的字符不可能到达守护进程,守护进程中常规方法(如 printf)输出的字符也不可能在终端上显示。所以通常关闭从 0 到 最大文件描述符的所有文件描述符。

示例

int main()
{
 const int MAXFD=64;
 int i=0;
 if(fork()!=0)//父进程退出
 exit(0);
 setsid(); //成为新进程组组长和新会话领导,脱离控制终端
 chdir("/"); //设置工作目录为根目录
 umask(0); //重设文件访问权限掩码
 for(;i<MAXFD;i++) //尽可能关闭所有从父进程继承来的文件
 close(i);
 //该进程已经成为了Daemon进程
}

你可能感兴趣的:(C,Linux)