【嵌入式学习】IO进程线程-Day4-进程线程基础

作业

1> 创建出三个进程完成两个文件之间拷贝工作,子进程1拷贝前一半内容,子进程2拷贝后一半内容,父进程回收子进程的资源

/*
 * Filename: errno.c
 * Author: linus
 * Date: 2023-12-29
 * Version: 1.0
 *
 * Description: 创建出三个进程完成两个文件之间拷贝工作,
 *              子进程1拷贝前一半内容,子进程2拷贝后一半内容,
 *              父进程回收子进程的资源
 */

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

int main(int argc, const char *argv[])
{
    // 判断输入是否正确
    if (argc != 3)
    {
        puts("输入有误!");
        return -1;
    }
    // 定义文件指针
    FILE *fp = 0;
    FILE *fp_cp = 0;
    // 以只读的形式打开文件
    if ((fp = fopen(argv[1], "r")) == NULL)
    {
        perror("fopen:");
        return -1;
    }

    // 以只写的形式打开文件
    if ((fp_cp = fopen(argv[2], "w")) == NULL)
    {
        perror("fopen:");
        return -1;
    }

    // 读文件大小
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);
    long half_size = file_size / 2;
    rewind(fp);

    // 创建出一个子进程
    pid_t pid1 = fork();
    if (pid1 == 0) // 子进程1
    {
        char buff;
        while (ftell(fp) < half_size)
        {
            int res = fread(&buff, 1, sizeof(buff), fp);
            fwrite(&buff, 1, res, fp_cp);
        }

        exit(EXIT_SUCCESS);
    }
    else if (pid1 == -1)
    {
        perror("fork error");
        return -1;
    }

    // 创建出另一个子进程
    pid_t pid2 = fork();
    if (pid2 == 0) // 子进程2
    {
        sleep(1);
        char buff;
        fseek(fp, half_size, SEEK_SET);
        fseek(fp_cp, half_size, SEEK_SET);
        while (!feof(fp))
        {
            int res = fread(&buff, 1, sizeof(buff), fp);
            fwrite(&buff, 1, res, fp_cp);
        }
        exit(EXIT_SUCCESS);
    }
    else if (pid1 == -1)
    {
        perror("fork error");
        return -1;
    }

    wait(NULL);
    wait(NULL);

    fclose(fp);
    fclose(fp_cp);

    return 0;
}

一、多进程

引入目的:让多个任务实现并发执行

1.1 进程概念

1、程序的一次运行过程称为进程

2、进程是有生命周期的,是一个动态过程,分为创建态、就绪态、运行态、阻塞态、消亡态

3、进程是资源分配的最小单位,系统会给每个进程分配4G的虚拟内存,其中0–3G的用户空间是独立的,3–4G的内核空间时共享的

4、进程是独立的,可以被任务器调度,调度的原则是时间片轮询、上下文切换

img

1.2 进程的内存管理

1> 系统会给每个进程分配4G的虚拟内存

2> 多个进程会独立拥有0–3G的用户空间,用户空间又分为栈区、堆区、静态区

3> 多个进程共享一份内核空间

4> 物理内存:内存条(硬件上)真正存在的存储空间

5> 虚拟内存:程序运行后,有4G的虚拟地址,由物理内存通过内存映射单元映射而来,在需要使用内存的时候,会映射到物理内存上

6> 在32位操作系统上,虚拟内存的空间是4G

​ 在64位系统上,虚拟内存的空间是256T = 2^48

img

1.3 进程和程序的区别

进程:进程是动态的,进程是程序的一次执行过程,有生命周期,进程会为自己分配内存空间,是资源分配的最小单位

程序:程序是静态的,没有所谓的生命周期,它是在磁盘上存放的二进制文件

1.4 进程的种类

进程一共分为三类:交互进程、批处理进程、守护进程

交互进程:他是由shell控制,可以直接与用户进行交互,例如文本编辑器

批处理进程:维护了一个队列,被放入队列中的进程会统一进行处理。例如gcc编译器的一步到位的编译

守护进程:脱离终端而存在的进程,随着系统的启动而运行,随着系统的关闭而结束。例如服务进程

1.5 进程的PID概念

PID:进程号(process ID)

PPID:父进程号

进程号是进程的唯一标识,他是一个大于等于0的一个整数,并且每个进程的进程号不会重复

每个进程都是继承父进程而得到的,所以每个进程都会有父进程

在linux系统中的根目录下的proc目录中,存放的以数字命名的都是一个进程

img

1.6 特殊的进程

1> 系统启动后,至少要运行三个特殊进程,进程号分别是 0、1、2

2> 0号进程:又称为 idel进程,他是有linux系统启动后的第一个进程,是1号和2号进程的父进程,这个进程也叫空闲进程,当系统中的其他进程都不执行时,运行该进程。

3> 1号进程:称为 init进程,是由0进程产生,完成一些系统启动时的必要初始化工作,也是孤儿进程的父进程,可以完成对孤儿进程的收尸工作

4> 2号进程:称为kthreadd进程,是右0号进程产生,用于调度相关进程,也称调度进程

5> 孤儿进程:当前进程还在运行,但是其父进程已经退出,该进程称为孤儿进程,会被init进程收养

6> 僵尸进程:当前进程已经运行结束,但是其父进程没有为其收尸

1.7 有关进程的shell指令

1> 查看进程信息指令:ps

ps -ef : 可以查看进程间的关系

UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 一月01 ?     00:00:25 /sbin/init splash
root          2      0  0 一月01 ?     00:00:00 [kthreadd]
root          3      2  0 一月01 ?     00:00:00 [rcu_gp]
root          4      2  0 一月01 ?     00:00:00 [rcu_par_gp]
root          6      2  0 一月01 ?     00:00:00 [kworker/0:0H-kb]
root          8      2  0 一月01 ?     00:00:00 [mm_percpu_wq]
root          9      2  0 一月01 ?     00:00:05 [ksoftirqd/0]
root         10      2  0 一月01 ?     00:01:05 [rcu_sched]
root         11      2  0 一月01 ?     00:00:01 [migration/0]
UID:当前进程的用户id
PID:当前进程的进程号
PPID:当前进程的父进程号
STIME:开始运行的日期
TTY:如果为问号,表明该进程不依赖于任何终端存在
CMD:进程名称

ps -ajx:可以查看进程的状态

  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
     0      1      1      1 ?            -1 Ss       0   0:25 /sbin/init splash
     0      2      0      0 ?            -1 S        0   0:00 [kthreadd]
     2      3      0      0 ?            -1 I<       0   0:00 [rcu_gp]
     2      4      0      0 ?            -1 I<       0   0:00 [rcu_par_gp]
     2      6      0      0 ?            -1 I<       0   0:00 [kworker/0:0H-kb]
     2      8      0      0 ?            -1 I<       0   0:00 [mm_percpu_wq]
     2      9      0      0 ?            -1 S        0   0:05 [ksoftirqd/0]
     2     10      0      0 ?            -1 I        0   1:05 [rcu_sched]
     2     11      0      0 ?            -1 S        0   0:01 [migration/0]
     2     12      0      0 ?            -1 S        0   0:00 [idle_inject/0]
 
PGID:当前进程所属组的id号
SID:当前进程所在会话组的id
TPGID:如果是-1,表示该进程是一个守护进程
STAT:当前进程的状态,一个状态包含主状态栏和附加态

ps -aux:可以查看当前进程所占内存和cpu的资源占有率

USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.3 225504  6920 ?        Ss   一月01   0:25 /sbin/init sp
root          2  0.0  0.0      0     0 ?        S    一月01   0:00 [kthreadd]
root          3  0.0  0.0      0     0 ?        I<   一月01   0:00 [rcu_gp]
root          4  0.0  0.0      0     0 ?        I<   一月01   0:00 [rcu_par_gp]
root          6  0.0  0.0      0     0 ?        I<   一月01   0:00 [kworker/0:0H
root          8  0.0  0.0      0     0 ?        I<   一月01   0:00 [mm_percpu_wq
root          9  0.0  0.0      0     0 ?        S    一月01   0:05 [ksoftirqd/0]
root         10  0.0  0.0      0     0 ?        I    一月01   1:05 [rcu_sched]
 
2> top指令:动态查看进程相关信息

htop指令:带颜色查看进程相关信息

img

3> 单独获取进程的进程号:pidof 进程名
4> 以树状图的形式显示进程间关系:pstree
5> 向进程发送信号指令:kill指令

使用格式:kill -信号号 pid

能够发送的信号,可以通过指令kill -l查看

 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	
 
特殊的信号:
SIGKILL(9):杀死指定进程信号,该信号,既不能被捕获,也不能被忽略
SIGHUP(1):当进程所在的终端关闭后,终端进程会给当前终端中的每个进程发送该信号,默认操作杀死进程
SIGINT(2):当用户按下ctrl + c后,进程会收到该信号
SIGQUIT(3):退出指定的进程,用户键入ctrl + \时产生
SIGSEGV(11):当指针使用不当或堆栈溢出时,内核空间发射该信号,表示段错误
SIGCHLD(17):当子进程退出后,会向父进程发送该信号,表示我已经死了
SIGCONT(18):让暂停的进程继续运行
SIGSTOP(19)、SIGTSTP(20):让进程暂停执行
 

二、进程状态

2.1 进程状态的描述

man ps

进程的主状态:
               D    uninterruptible sleep (usually IO)    不可中断的休眠态,通常是进行IO操作
               R    running or runnable (on run queue)    运行态
               S    interruptible sleep (waiting for an event to complete)  可中断的休眠态
               T    stopped by job control signal       停止,由信号控制
               t    stopped by debugger during the tracing  停止态,调试时的停止态
               W    paging (not valid since the 2.6.xx kernel)  已经弃用的状态
               X    dead (should never be seen)               死亡态,不会被看到
               Z    defunct ("zombie") process, terminated but not reaped by its parent   僵尸态,已经退出,但是父进程没有收尸               
附加态:
               <    high-priority (not nice to other users)     高优先级的进程
               N    low-priority (nice to other users)          低优先级的进程
               L    has pages locked into memory (for real-time and custom IO)  锁在内存中的进程,不会进入swap分区
               s    is a session leader                         会话组组长
               l    is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)   包含多线程的进程
               +    is in the foreground process group           前台运行的进程  
 

2.2 进程状态的切换

进程主要的状态一共有五种:创建态、就绪态、运行态、阻塞态、死亡态

img

2.3 具体状态转换的实例

运行一个可执行程序,并查看其状态

查看后台运行进程的作业号:jobs

将停止的进程,切换到后台运行:bg 作业号

将后台运行的进程,切换到前台运行:fg 作业号

直接将程序运行于后台:./a.out &

img

休眠进程的实例

img

三、多进程编程

3.1 创建进程

1> 进程创建过程是子进程拷贝父进程的资源,进而产生一个独立的进程个体,子进程会拥有父进程在创建进程之前的所有资源

2> 创建进程的api函数 fork

       #include <sys/types.h>
       #include <unistd.h>
 
       pid_t fork(void);
功能:创建出一个子进程
参数:无
返回值:在父进程中,该函数返回子进程的pid号,在子进程中该函数返回0,失败返回-1并置位错误码,并且不会创建出子进程
注意:当子进程创建出来后,父子进程都会执行fork之后的语句

3> 不关注返回值的情况时

img

img

不关注返回值情况时,n个fork会产生2^n个进程,并且多个进程之间没有先后顺序执行

4> 关注返回值

#include<myhead.h>
 
int main(int argc, const char *argv[])
{
	pid_t pid = -1;   //定义变量存储进程号
 
	pid = fork();    //创建一个子进程
 
	//后续的程序,父子进程都会执行
	if(pid > 0)
	{
		printf("这是父进程,pid = %d\n", pid);
	}else if(pid == 0)
	{
		printf("这是子进程,pid = %d\n", pid);
	}else 
	{
		perror("fork error");
		return -1;
	}
 
	printf("hello world\n");
	while(1);
 
 
	return 0;
}
 

img

5> 验证多个任务能够并发执行

#include<myhead.h>
 
int main(int argc, const char *argv[])
{
	pid_t pid = -1;
 
	//创建出子进程
	pid = fork();
 
	if(pid > 0)
	{
		//父进程要执行的任务
		char sendbuf[128] = "";
 
		while(1)
		{
			scanf("%s", sendbuf);
			printf("您发送的数据%s\n", sendbuf);
			if(strcmp(sendbuf, "quit") == 0)
			{
				break;
			}
		}
 
 
	}else if(pid == 0)
	{
		//子进程要执行的任务
		char rcvbuf[20] = "";
 
		//打开文件
		int fd = open("./01test.c", O_RDONLY);
		if(fd == -1)
		{
			perror("open error");
			return -1;
		}
 
		while(1)
		{
			memset(rcvbuf, 0, sizeof(rcvbuf));  //清空内容
			int res = read(fd, rcvbuf, sizeof(rcvbuf));
			write(1, rcvbuf, res);    //输出到终端
			if(res == 0)
			{
				break;
			}
 
			sleep(2);
		}
 
 
	}else
	{
		perror("fork error");
		return -1;
	}
 
 
	while(1);
 
	return 0;
}
 

6> 验证子进程拷贝父进程在fork之前的资源,fork后,父子进程独立

#include<myhead.h>
 
int main(int argc, const char *argv[])
{
	pid_t pid = -1;
	int num = 520;    //父进程的资源
 
	//创建子进程
	pid = fork();
	if(pid > 0)
	{
		//父进程
		num = 1314;
		printf("父进程中num = %d, %p\n", num, &num);    //1314
 
	}else if(pid == 0)
	{
		//子进程
		printf("子进程中num = %d, %p\n", num, &num);     //520
	}else
	{
		perror("fork error");
		return -1;
	}
 
	while(1);
 
	return 0;
}
 

7> 写时拷贝技术

img

3.2 getpid/getppid 进程号的获取

       #include <sys/types.h>
       #include <unistd.h>
 
       pid_t getpid(void);
       功能:获取当前进程的pid号
       参数:无
       返回值:成功返回当前进程的pid号,不会失败
       
       pid_t getppid(void);
       功能:获取当前进程的父进程的pid号
       参数:无
       返回值:成功返回当前进程的父进程pid号,不会失败       
 
#include<myhead.h>
 
int main(int argc, const char *argv[])
{
	pid_t pid = -1;
 
	//创建子进程
	pid = fork();
	if(pid > 0)
	{
		//父进程
		printf("父进程中:pid=%d, child pid=%d, parent pid=%d\n",\
				getpid(), pid, getppid());   
 
	}else if(pid == 0)
	{
		//子进程
		printf("子进程中:pid=%d, parent pid=%d\n", \
				getpid(), getppid());    
	}else
	{
		perror("fork error");
		return -1;
	}
 
	while(1);
 
	return 0;
}
 

3.3 exit/_exit 进程退出函数

       #include <stdlib.h>
 
       void exit(int status);
功能:刷新标准io的缓冲区后,退出进程
参数:进程退出时的状态,会将 status&0377的结果返回给父进程
        EXIT_SUCCESS(0):表示成功退出
        EXIT_FAILURE(1):表示失败退出
返回值:无
 
 
       #include <unistd.h>
 
       void _exit(int status);
功能:不刷新标准io的缓冲区,直接退出进程
参数:进程退出时的状态,会将 status&0377的结果返回给父进程
        EXIT_SUCCESS(0):表示成功退出
        EXIT_FAILURE(1):表示失败退出
返回值:无
#include<myhead.h>
 
int main(int argc, const char *argv[])
{
	pid_t pid = -1;
 
	//创建子进程
	pid = fork();
	if(pid > 0)
	{
		//父进程
		printf("父进程中:pid=%d, child pid=%d, parent pid=%d\n",\
				getpid(), pid, getppid());   
 
	}else if(pid == 0)
	{
		//子进程
		printf("子进程中:pid=%d, parent pid=%d\n", \
				getpid(), getppid());    
 
		sleep(3);
		
		//输出一组数据
		printf("1111111111111111111111");
		//exit(EXIT_SUCCESS);                //刷新标准io的缓冲区后退出进程
		_exit(EXIT_SUCCESS);                //不刷新标准io的缓冲区后退出进程
 
 
 
	}else
	{
		perror("fork error");
		return -1;
	}
 
	while(1);
 
	return 0;
}
 

3.4 wait/waitpid 进程资源的回收

       #include <sys/types.h>
       #include <sys/wait.h>
 
       pid_t wait(int *wstatus);
       功能:阻塞等待子进程的结束并回收子进程的资源,如果子进程不退出,则父进程会一直在该函数处阻塞
       参数:接受子进程退出时的状态,一般填NULL,表示不接收
       返回值:成功返回退出的子进程的pid号,失败返回-1并置位错误码
       
 
       pid_t waitpid(pid_t pid, int *wstatus, int options);
       功能:可以阻塞也可以非阻塞形式回收子进程的资源
       参数1:进程号
               >0:表示回收特定的子进程的资源(常用)
               =0:回收当前进程所在进程组中的任意一个子进程
               =-1:表示回收任意一个子进程 (常用)
               <-1:表示回收其他组(组id为pid的绝对值)中的任意一个子进程
       参数2:接收子进程退出时的状态,一般填NULL,表示不接收
       参数3:是否阻塞的选项
               0:表示阻塞
               WNOHANG:表示非阻塞回收
       返回值:>0:表示成功回收一个子进程,返回该子进程的pid
               =0:非阻塞回收资源时,没有子进程退出,该函数返回0
               =-1:失败返回-1并置位错误码                                     
 
1> wait案例实现

img

2> waitpid未回收子进程

img

3> 使用waitpid回收僵尸进程

img

3.5 创建三个进程

#include<myhead.h>
 
int main(int argc, const char *argv[])
{
	pid_t pid1 = -1;
	//创建出一个子进程
	pid1 = fork();
	if(pid1 == 0)
	{
		printf("我是老大\n");
		sleep(2);
		exit(EXIT_SUCCESS);
	}else if(pid1 > 0)
	{
		//创建一个子进程
		pid_t pid2 = fork();
		if(pid2 == 0)
		{
			printf("我是老二\n");
			sleep(5);
			exit(EXIT_SUCCESS);
		}else if(pid2 > 0)
		{
			printf("我是父进程,我来回收资源\n");
			wait(NULL);
			wait(NULL);
 
			printf("孩子都死了,我也不活了\n");
		}else
		{
			perror("fork error");
			return -1;
		}
	}else
	{
		perror("fork error");
		return -1;
	}
 
	return 0;
}
 
  • 25
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值