C语言-进程控制编程

1、 进程的基本概念

进程的分类

交互进程

批处理进程

守护进程:一般在后台运行,一般由操作系统在开机时通过脚本自动激活启动或由超级管理用户root来启动

进程的属性

进程ID:进程的唯一数值,用来区分进程

启动进程的用户ID(UID)和归属的组(GID)

进程状态:运行(R)、休眠(S)、僵尸(Z)

进程执行优先级

进程所连接的终端名

进程资源占用

进程监视工具:ps

一般用法:ps -aux|more //用管道和more命令连接起来分页查看

其他用法:ps aux|grep 程序名 //提取指定程序的进程

查询进程工具:pgrep

ps 参数选项 程序名

常用参数如下:

-1: 列出程序名和进程ID

-o: 进程起始的ID

-n: 进程终止的ID

Linux进程的三态

就绪状态:若进程已被分配到除CPU以外所有必要的资源,只要获得处理器便可立即执行

执行状态:进程已获得处理器,其进程正在处理器上执行

阻塞状态:当正在执行的进程,由于等待某个事件发生而无法执行时,便处于阻塞状态

Linux进程调度算法

FCFS算法:也叫FIFO算法,先来先处理

时间轮片算法:对FIFO算法的改进,周期性的进行进程切换

STCF算法:短任务优先算法,核心是所有程序都有一个优先级,短任务的优先级比长任务的高,OS总是安排优先级高的进程先运行

2、 进程创建

2.1 getgid函数和getpid()函数

函数详解

cpp 复制代码
表头文件
    #include <unistd.h>
    #include <sys/types.h>
    
定义函数
    gid_t getgid(void)
    
函数说明
    用来获得执行目前进程的组识别码
    
返回值
    组识别码(进程的ID)
    
pid_t getpid(void)
获取当前进程ID

2.2 getppid函数

函数详解

cpp 复制代码
表头文件
    #include <unistd.h>
    
定义函数
    pid_t getppid(void)
    
函数说明
    获得目前进程的父进程识别码
    
返回值
    目前进程的父进程识别码

综合案例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc,char *argv[])
{
	printf("My parent 'pid=%d'\n",getppid());
	printf("My 'pid=%d'\n",getpid());
	return 0;
}

运行结果

Kotlin 复制代码
linux@ubuntu:~/test$ gcc get_dome.c -o get_dome -Wall
linux@ubuntu:~/test$ ./get_dome
My parent 'pid=14743'
My 'pid=14846'

2.3 fork函数(创建子进程)

函数详解

cpp 复制代码
表头函数
    #include <sys/types.h>
    #include <unistd.h>
    
定义函数
    pid_t fork(void)
    
函数说明
    创建子进程,子进程完全复制父进程的资源,复制出的子进程有自己的task_struct结构和PID,复制父进程其他所有的资源
    现在Linux中采用写时复制技术,即并不会立即复制,若后来确实发生了写入,父子进程的数据不一致,于是产生了复制动作,若直接调用exec执行另一个可执行文件,那将不会进行复制
    父子进程谁先运行不确定,进行是是同步的
    
返回值
    在父进程中返回子进程号,在子进程中返回0,错误返回-1

综合案例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
	pid_t child;
	int i = 0;

	/*创建子进程*/
	if((child = fork()) == -1)
	{
		printf("fork ERRO\n");
		exit(1);
	}
	else if(child == 0)
	{
		printf("my is child:%d\n",getpid());
		i = 18;
		printf("child i exit with %d\n",i);
	}
	else
	{
		printf("my is father:%d\n",getpid());
		i = 41;
		sleep(1);
		printf("father i exit with %d\n",i);
	}
	printf("father and child have %d\n",getpid());
	return 0;
}

运行结果

cpp 复制代码
linux@ubuntu:~/test$ gcc fork_dome.c -o fork_dome -Wall
linux@ubuntu:~/test$ ./fork_dome 
my is father:15057               //父进程执行打印,给i赋值,然后睡眠
my is child:15058                //子进程执行打印
child i exit with 18             //子进程给i赋值,打印
father and child have 15058      //子进程出了if判断后的打印
father i exit with 41            //父进程睡眠完了,虽然子进程给i另外赋值了,但fork创建的进程有自己独立的空间,所以子进程i的赋值不影响父进程
father and child have 15057
linux@ubuntu:~/test$ 

2.4 vfork函数

函数详解

cpp 复制代码
表头函数
    #include <unistd.h>
    
定义函数
    pid_t vfork(void)
    
函数说明
    创建子进程,与fork不同的是,进程是调用进程的一个副本,共享父进程的地址空间,
    它会保证子进程先运行,子进程运行的时候会暂停父进程的执行,
    直到子进程调用子进程调用exec族,或者exit()结束,父进程才会继续执行
    
返回值
    成功在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中返回0。如果失败直接返回-1

综合案例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
	pid_t child;
	int i = 0;

	/*创建子进程*/
	if((child = vfork()) == -1)
	{
		printf("fork ERRO\n");
		exit(1);
	}
	else if(child == 0)
	{
		printf("my is child:%d\n",getpid());
		i = 18;
		sleep(1);
		printf("child i exit with %d\n",i);
		exit(1);
	}
	else
	{
		printf("my is father:%d\n",getpid());
		sleep(2);
		printf("father i exit with %d\n",i);    //i的值会因为子进程的中的改变而改变,在fork中会是0
	}
	printf("father and child have %d\n",getpid());
	return 0;
}

运行结果

Kotlin 复制代码
linux@ubuntu:~/test$ gcc vfork_dome.c -o vfork_dome -Wall
linux@ubuntu:~/test$ ./vfork_dome 
my is child:15154                  //先执行子进程,父进程挂起
child i exit with 18
my is father:15153
father i exit with 18              //父进程会因为子进程改变变量而改变
father and child have 15153

2.5 启动进程:exec族

int execl(const char *path,const char *arg,...);

int execlp(const char *file,const char *arg,...);

int execle(const char *path,const char *arg,...,char *const envp[]);

int execv(const char *path,char * const argv[]);

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

int execve(const char *path,char * const argv[],char *const envp[]); //真正意义上的系统调用,其他都是再次基础上封装

2.5.1 execl函数

函数详解

cpp 复制代码
头文件
    #include <unistd.h>
    
定义函数
    int execl(const char *path,const char *arg,...)
    
函数说明
    path 函数所代表的文件路径 接下的代表执行该文件传送过去的参数
    
返回值
    执行成功函数不会返回,执行失败则返回-1

2.5.2 execlp函数

函数详解

cpp 复制代码
头文件
    #include <unistd.h>
    
定义函数
    int execlp(const char *file,const char *arg,...)
    
函数说明
    从PATH环境变量所指目录中查找符合参数file的文件名 接下来的代表执行该文件传送过去的参数
    
返回值
    成功不会返回 执行失败则直接返回-1

综合案例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc,char *argv[])
{
	pid_t child;

	if((child = fork()) < 0)
	{
		printf("vfork error!\n");
		exit(-1);
	}
	if(child == 0)
	{
		printf("my is child\n");
		if(-1 == execlp("./a","hello","nihao",NULL))
		{
			printf("execlp error!\n");
		}
	}
	else
	{
		printf("my is father,a.c comletion\n");
	}
	return 0;
}

运行结果

Kotlin 复制代码
linux@ubuntu:~/test$ gcc a.c -o a -Wall
linux@ubuntu:~/test$ ./exec_dome
my is father,a.c comletion
my is child
linux@ubuntu:~/test$ 我是a程序,将打印传入的参数.
0:hello
1:nihao

2.5.3 execve函数(真正意义上的系统调用)

cpp 复制代码
表头文件
    #include <unistd.h>
    
定义函数
    int execve(const char *path,char * const argv[],char *const envp[])
    
函数说明
    filename 文件路径包含文件名
    参数利用数组指针来传递给执行文件 末尾为(char*)0
    传递给执行文件新的环境变量,末尾为0
    
返回值
    成功不会返回 执行失败则直接返回-1

2.5.4 execvp函数

cpp 复制代码
表头文件
    #include <unistd.h>
    
定义函数
    int execvp(const char *file,char * const argv[])
    
函数说明
    从PATH环境变量所指目录中查找符合参数file的文件名
    argv数组  存储可执行文件执行时需要的参数
    
返回值
    成功不会返回 执行失败则直接返回-1

2.6 system函数

函数详解

cpp 复制代码
头文件
    #include <stdlib.h>
    
定义函数
    int system(const char * string)
    
函数说明
    用于执行shell命令
    system()会调用fork()产生子进程,由子进程调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程
    在system()调用期间,SIGCHLD信号会被暂时搁置,SIGINT和SIGOUIT信号则会被忽略
    
返回值
    当在调用/bin/sh时失败返回127,其他原因失败返回-1,若string为空指针,返回非零值

综合案例

cpp 复制代码
#include <stdlib.h>

int main(int argc,char *argv[])
{
	system("ls -al");
	return 0;
}

运行结果

cpp 复制代码
linux@ubuntu:~/test$ gcc system_dome.c -o system_dome
linux@ubuntu:~/test$ ./system_dome 
total 92
drwxrwxr-x  2 linux linux  4096  9月 27 16:09 .
drwxr-xr-x 32 linux linux  4096  9月 27 14:23 ..
-rwxrwxr-x  1 linux linux  7193  9月 27 14:22 a
-rw-rw-r--  1 linux linux   197  9月 27 14:22 a.c
-rwxrwxr-x  1 linux linux  7273  9月 27 14:20 exec_dome
-rw-rw-r--  1 linux linux   382  9月 27 14:23 exec_dome.c
-rwxrwxr-x  1 linux linux  7348  9月 27 13:23 fork_dome
-rw-rw-r--  1 linux linux   543  9月 27 13:23 fork_dome.c
-rwxrwxr-x  1 linux linux  7241  9月 27 12:58 get_dome
-rw-rw-r--  1 linux linux   188  9月 27 12:59 get_dome.c
-rwxrwxr-x  1 linux linux  7167  9月 27 16:09 system_dome
-rw-rw-r--  1 linux linux    87  9月 27 16:09 system_dome.c
-rw-------  1 linux linux 12288  9月 27 16:09 .system_dome.c.swp
-rwxrwxr-x  1 linux linux  7350  9月 27 13:52 vfork_dome
-rw-rw-r--  1 linux linux   557  9月 27 13:53 vfork_dome.c

2.7 wait函数和waitpid函数

函数详解

cpp 复制代码
头文件
    #include <sys/types.h>
    #include <sys/wait.h>
    
定义函数
    pid_t wait(int *status)
    
函数说明
    会暂停目前进程的执行,直到有信号到来或子进程结束为止。
    status  返回子进程的结束状态值,可设置为NULL
    通过WEXITSTATUS(status)来获取结束值
    通过WIFEXITED(status) 判断是否为正常结束,正常结束为非0值
    
返回值
    执行成功返回值子进程识别码(PID),错误返回-1

综合案例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc,char *argv[])
{
	pid_t pid;
	int status,i;

	if(fork() == 0)
	{
		printf("tis is the child process .pid=%d\n",getpid());
		exit(5);
	}
	else
	{
		sleep(1);
		printf("this is the parent process.wait for child...\n");
		pid=wait(&status);
		i = WEXITSTATUS(status);
		printf("child's pid=%d.eixt status=%d\n",pid,i);
	}

	return 0;
}

运行结果

Kotlin 复制代码
linux@ubuntu:~/test$ gcc wait_dome.c -o wait_dome
linux@ubuntu:~/test$ ./wait_dome 
tis is the child process .pid=24163
this is the parent process.wait for child...
child's pid=24163.eixt status=5

函数详解

cpp 复制代码
头文件
    #include <sys/types.h>
    #include <sys/wait.h>
    
定义函数
    pid_t waitpid(pid_t pid,int *status,int options)
    
函数说明
    用于等待指定ID子进程中断或结束
    pid 欲等待的子进程识别码
        pid<-1:等待进程组识别码为pid绝对值的任何进程
        pid=-1:等待任何子进程,相当于wait函数
        pid=0: 等待进程组识别与目前进程相同的任何子进程
        pid>0: 等待任何子进程识别码为pid的子进程
    status  返回子进程的结束状态值
    options 为0或下面数值的OR运算(|)组合
        WNOHANG: 没有任何已经结束的子进程则马上返回,不予等待
        WUNTRACED: 如果子进程进入暂停执行状态则马上返回,但结束状态不予理会
        
返回值
    执行成功返回子进程识别码(PID),错误返回-1

2.8 exit函数和_exit函数

cpp 复制代码
表头文件
    #include <stdlib.h>
    
定义函数
    void exit(int status)
    
函数说明
    用于正常结束目前进程的执行,并把参数status返回给父进程
    而所有的缓冲区数据会自动写回并关闭未关闭的文件
​
返回值
    无
_exit同exit函数
不同点:
    传递SIGCHLD信号给父进程,不处理标准I/O缓冲区

3、 守护进程

3.1 守护进程的基本概念

cpp 复制代码
进程组:
    一个或多个进程的集合,每个进程都有一个进程组ID,这个ID就是进程组长的ID
会话期:
    一个或多个进程组的集合,每个会话有唯一一个会话首进程,会话ID为会话进程ID
控制终端:
    每一个会话可以有一个单独的控制终端,与控制终端连接的会话首进程就是控制进程
    
创建守护进程过程中用到的一个关键函数------setsid
    #include <unistd.h>
    pid_t setsid(void);
    用于创建一个新的会话期,实现以下效果:
        摆脱原会话的控制,该进程变成新会话期的首进程
        摆脱原进程组,称为一个新进程组的组长
        摆脱终端控制

3.2 创建守护进程的一般步骤

cpp 复制代码
(1) 通过fork函数创建子进程,父进程通过exit函数退出
(2) 在子进程中调用setsid函数,创建新的会话
(3) 再次通过fork函数创建一个子进程并让父进程退出
(4) 在子进程中调用chdir函数,让根目录"/"称为子进程的工作目录
(5) 在子进程中调用umask函数,设置进程的文件权限掩码为0
(6) 在子进程中关闭任何不需要的文件描述符
(7) 守护进程退出处理(结束)
​
利用库函数deamon创建守护进程
#include <unistd.h>
int daemon(int nochdir,int noclose);

综合案例

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>


static int flag = 1;
void create_deamon();                   //守护进程创建函数
void handler(int);                      //接受信号结束句柄

int main(int argc,char *argv[])
{
	time_t t;
	int fd;
	create_deamon();
	struct sigaction act;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	if(sigaction(SIGQUIT,&act,NULL))
	{
		printf("sigaction error.\n");
		exit(0);
	}
	while(flag)
	{
		fd = open("/home/linux/test/dae.log",O_WRONLY|O_CREAT|O_APPEND,0644);
		if(fd == -1)
		{
			printf("open error\n");
		}
		t = time(0);
		char *buf = asctime(localtime(&t));
		write(fd,buf,strlen(buf));
		close(fd);
		sleep(10);
	}
	return 0;
}

void handler(int sig)
{
	printf("I got a signal %d\nI'm quitting.\n",sig);
	flag = 0;
}

void create_deamon(){              //守护进程创建函数
	pid_t pid;
	pid = fork();

	if(pid == -1)
	{
		printf("fork error\n");
		exit(1);
	}
	else if(pid)
	{
		exit(0);
	}

	if(-1 == setsid())
	{
		printf("setsid error\n");
		exit(1);
	}

	pid = fork();
	if(pid == -1)
	{
		printf("fork error\n");
		exit(1);
	}
	else if(pid)
	{
		exit(0);
	}

	chdir("/");
	int i;
	for(i = 0;i < 3;++i)
	{
		close(i);
	}
	umask(0);
	return;
}

运行结果

cpp 复制代码
linux@ubuntu:~/test$ gcc deamon_dome.c -o deamon_dome -Wall
linux@ubuntu:~/test$ ./deamon_dome 
linux@ubuntu:~/test$ cat dae.log
Sun Sep 29 13:22:50 2024
Sun Sep 29 13:23:00 2024
Sun Sep 29 13:23:10 2024
linux@ubuntu:~/test$ ps -ef|grep 'deamon_dome'
linux    24622     1  0 13:22 ?        00:00:00 ./deamon_dome
相关推荐
玛丽亚后3 分钟前
秒懂Linux之线程
linux·开发语言·jvm·c++
计时开始不睡觉16 分钟前
【微信小程序前端开发】入门Day03 —— 页面导航、事件、生命周期、WXS 脚本及自定义组件
开发语言·前端·javascript·微信小程序
小媛早点睡24 分钟前
day02笔试练习
java·开发语言·算法
倔强的石头10626 分钟前
【C语言指南】数据类型详解(下)——自定义类型
c语言·开发语言·html
Stark、28 分钟前
《数据结构》--链表【包含跳表概念】
开发语言·数据结构·c++
冷白白30 分钟前
【C++】单例模式
开发语言·c++·单例模式·c
薰衣草233333 分钟前
java部分总结
java·开发语言
s_little_monster34 分钟前
【C++】多态(下)
开发语言·c++·经验分享·笔记·学习
杰哥在此43 分钟前
Python知识点:如何使用Raspberry Pi与Python进行边缘计算
开发语言·python·面试·编程·边缘计算
北沐xxx1 小时前
VMware虚拟机Centos操作系统——配置docker,运行本地打包的镜像,进入conda环境(vmware,docker新手小白)
开发语言·python·docker·centos·conda