8.Linux进程控制|进程创建|进程终止|进程等待|进程程序替换|简易shell实现

【Linux】---进程控制(创建、终止、等待、替换) 进程创建、进程终止、进程等待、进程替换 阅读详情

进程创建

fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>  
pid_t fork(void);  
//返回值:自进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
    ![[Pasted image 20260213162221.png]]

当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序

int main( void )  
{  
	pid_t pid;  
	printf("Before: pid is %d\n", getpid());
	
	if ( (pid=fork()) == -1 )perror("fork()"),exit(1);  
	printf("After:pid is %d, fork return %d\n", getpid(), pid);  
	sleep(1);  
	
	return 0;  
}  

运行结果:  
[root@localhost linux]# ./a.out  
Before: pid is 43676  
After:pid is 43676, fork return 43677  
After:pid is 43677, fork return 0

这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

fork函数返回值
  • 子进程返回0,
  • 父进程返回的是子进程的pid。
写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
![[Pasted image 20260213162731.png]]

![[Pasted image 20260213181552.png]]

运行时出问题
字符串常量是具有常性的,在字符常量区不能对对应的常量数据进行修改
hello Linux保存的是字符串的起始地址,这个地址是虚拟地址,指针解引用进行修改写入时,一定要进行虚拟到物理地址的转化,页表中映射的条目是只读位,页表不让映射,所以写入失败
操作系统层面上不让进行修改,和语言没关系

fork常规用法
  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork调用失败的原因
  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

进程终止

main函数的返回值就是进程的退出码,用来表明当前进程的执行情况
0表示成功,非0表示失败
其他函数退出,仅仅表示函数调用完毕

echo $?

bash内部会记录最近一次进程执行完毕时的退出码
![[Pasted image 20260214043653.png]]

![[Pasted image 20260214045907.png]]

![[Pasted image 20260214050048.png]]

![[Pasted image 20260214050524.png]]

自定义
![[Pasted image 20260214052034.png]]
![[Pasted image 20260214052344.png]]
![[Pasted image 20260214052359.png]]

![[Pasted image 20260214134545.png]]
![[Pasted image 20260214134555.png]]

其他函数退出,仅仅表示函数调用完毕,后面的printf也执行了

errno:除了进程退出,函数退出,通过函数的返回值知道函数的执行情况
调用函数通常看到两种结果

  1. 函数的执行结果–FILE*,NULL
  2. 函数的执行情况–成功、失败、什么原因
    ![[Pasted image 20260214135503.png]]
    ![[Pasted image 20260214135544.png]]

函数有错误码

![[Pasted image 20260214140128.png]]

![[Pasted image 20260214140144.png]]

进程退出场景
  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止
    进程出现异常的本质是进程收到了异常信号,每个信号都有不同的编号,不同的信号编号表示不同的异常的原因
    ![[Pasted image 20260214175101.png]]
    ![[Pasted image 20260214180056.png]]
    ![[Pasted image 20260214180214.png]]

![[Pasted image 20260214181855.png]]
![[Pasted image 20260214182431.png]]
![[Pasted image 20260214182459.png]]

![[Pasted image 20260214182712.png]]
![[Pasted image 20260214182745.png]]

任何进程最终的执行情况,可以使用两个数字表明具体执行的情况

  1. 信号编号signumber
  2. 进程退出码exit_code
进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

  1. 从main返回
  2. 调用exit
    ![[Pasted image 20260215011217.png]]

![[Pasted image 20260215011308.png]]
![[Pasted image 20260215011406.png]]

exit就是用来终止进程的,exit(退出码)
在进程代码中,任意地方调用exit,都表示进程退出
3. _exit
异常退出:

  • ctrl + c,信号终止
_exit函数
#include <unistd.h>  
void _exit(int status);  
//参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

exit函数
#include <unistd.h>  
void exit(int status);

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit
    ![[Pasted image 20260213163219.png]]
int main()  
{  
	printf("hello");  
	exit(0);  
}  

运行结果:  
[root@localhost linux]# ./a.out  
hello[root@localhost linux]#  

int main()  
{  
	printf("hello");  
	_exit(0);  
}  

运行结果:  
[root@localhost linux]# ./a.out  
[root@localhost linux]#

exit支持刷新缓冲区,_eixt不支持

return退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

进程等待

进程等待必要性
  • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待的方法

wait方法

#include<sys/types.h>  
#include<sys/wait.h>  
pid_t wait(int*status); 
 
返回值:  
成功返回被等待进程pid,失败返回-1。  
参数:  
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

![[Pasted image 20260215094333.png]]
![[Pasted image 20260215093934.png]]
![[Pasted image 20260215100359.png]]
![[Pasted image 20260215095844.png]]
![[Pasted image 20260215100509.png]]

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);  
返回值:  
	当正常返回的时候waitpid返回收集到的子进程的进程ID;  
	如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0; 
	如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;  
参数:  
	pid:  
		Pid=-1,等待任一个子进程。与wait等效。  
		Pid>0.等待其进程ID与pid相等的子进程。  
	status:  
		WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)  
		WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)  
	options:  
		WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

![[Pasted image 20260215103656.png]]
![[Pasted image 20260215104525.png]]

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。
    ![[Pasted image 20260213164613.png]]
获取子进程status
  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
    status在函数中是整数,返回的是两方面的信息,退出码和退出信号,这个整数是局部用类似位图的方式进行使用,status有自己的格式
    ![[Pasted image 20260213164806.png]]
测试代码:  
#include <sys/wait.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <errno.h>  
int main( void )
{  
	pid_t pid;  
	if ( (pid=fork()) == -1 )  
		perror("fork"),exit(1);  
	if ( pid == 0 ){  
		sleep(20);  
		exit(10);  
	} else {  
		int st;  
		int ret = wait(&st);  
		if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出  
			printf("child exit code:%d\n", (st>>8)&0XFF);  
		} else if( ret > 0 ) { // 异常退出  
			printf("sig code : %d\n", st&0X7F );  
		}  
	}  
}  

测试结果:  
[root@localhost linux]# ./a.out #等20秒退出  
child exit code:10  
[root@localhost linux]# ./a.out #在其他终端kill掉  
sig code : 9

![[Pasted image 20260215133644.png]]
![[Pasted image 20260215133751.png]]

![[Pasted image 20260215134203.png]]
![[Pasted image 20260215134147.png]]

加入除0错误
![[Pasted image 20260215140244.png]]
![[Pasted image 20260215140315.png]]

添加野指针
![[Pasted image 20260215140820.png]]
![[Pasted image 20260215140922.png]]

通过宏查看status
![[Pasted image 20260215143438.png]]
![[Pasted image 20260215144335.png]]

具体代码实现
  • 进程的阻塞等待方式:
int main()  
{  
	pid_t pid;  
	pid = fork();  
	if(pid < 0){  
		printf("%s fork error\n",__FUNCTION__);  
		return 1;  
	} else if( pid == 0 ){ //child  
		printf("child is run, pid is : %d\n",getpid());  
		sleep(5);  
		exit(257);  
	} else{  
		int status = 0;  
		pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S  
		printf("this is test for wait\n");  
		if( WIFEXITED(status) && ret == pid ){  
			printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));  
		}else{  
			printf("wait child failed, return.\n");  
			return 1;  
		}
	}  
	return 0;  
}  

运行结果:  
[root@localhost linux]# ./a.out  
child is run, pid is : 45110  
this is test for wait  
wait child 5s success, child return code is :1.

![[Pasted image 20260215133644.png]]

  • 进程的非阻塞等待方式:
#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <sys/wait.h>  
int main()  
{  
	pid_t pid;  
	pid = fork();  
	if(pid < 0){  
		printf("%s fork error\n",__FUNCTION__);  
		return 1;  
	}else if( pid == 0 ){ //child  
		printf("child is run, pid is : %d\n",getpid());  
		sleep(5);  
		exit(1);  
	} else{  
		int status = 0;  
		pid_t ret = 0;  
		do  
		{  
			ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待  
			if( ret == 0 ){  
				printf("child is running\n");  
			}  
			sleep(1);  
		}while(ret == 0);  
			if( WIFEXITED(status) && ret == pid ){  
				printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));  
			}else{  
				printf("wait child failed, return.\n");  
			return 1;  
		}  
	}  
	return 0;  
}

![[Pasted image 20260215154424.png]]
![[Pasted image 20260215154739.png]]

例子
![[Pasted image 20260215160234.png]]
![[Pasted image 20260215160402.png]]
![[Pasted image 20260215160649.png]]
![[Pasted image 20260215160823.png]]

进程程序替换

替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
![[Pasted image 20260213170635.png]]

替换函数

其实有六种以exec开头的函数,统称exec函数:

#include <unistd.h>`  

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[]);

函数解释
  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。
命名理解

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量
函数名参数格式是否带路径是否使用当前环境变量
execl列表不是是
execlp列表是是
execle列表不是不是,须自己组装环境变量
execv数组不是是
execvp数组是是
execvp数组不是不是,须自己组装环境变量
exec调用举例如下:
#include <unistd.h>  
int main()  
{  
	char *const argv[] = {"ps", "-ef", NULL};  
	char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
	  
	execl("/bin/ps", "ps", "-ef", NULL);  
	
	// 带p的,可以使用环境变量PATH,无需写全路径  
	execlp("ps", "ps", "-ef", NULL);  
	
	// 带e的,需要自己组装环境变量  
	execle("ps", "ps", "-ef", NULL, envp);  
	
	execv("/bin/ps", argv);  
	
	// 带p的,可以使用环境变量PATH,无需写全路径  
	execvp("ps", argv);  
	
	// 带e的,需要自己组装环境变量  
	execve("/bin/ps", argv, envp); 
	 
	exit(0);  
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
下图exec函数族 一个完整的例子
![[Pasted image 20260213171252.png]]

例子

![[Pasted image 20260216105746.png]]
![[Pasted image 20260216110141.png]]
![[Pasted image 20260216110530.png]]

可以用自己的程序执行系统指令
![[Pasted image 20260216112359.png]]
![[Pasted image 20260216112554.png]]

![[Pasted image 20260216113320.png]]
![[Pasted image 20260216113444.png]]

  1. 程序替换一旦成功,exec*后续的代码不再执行,因为被替换掉了
  2. exec*只有失败返回值,没有成功返回值
  3. 替换完成,不创建新的程序
  4. 创建一个进程,先创建pcb,地址空间,页表等;再把程序加载到内存。程序替换的本质工作就是加载
  5. 标准传参,也可以非标准传参
  • 多进程版本
    ![[Pasted image 20260218000217.png]]
    ![[Pasted image 20260218000532.png]]

因为子进程进行代码和数据替换时,因为进程的独立性,为了不影响父进程,进行写时拷贝
写时拷贝的本质就是开辟新的空间

execlp,p:PATH,只需要告诉程序的名字,系统替换的时候会自动去PATH环境变量去查找
![[Pasted image 20260218091823.png]]
![[Pasted image 20260218091808.png]]

execv,
![[Pasted image 20260218092923.png]]

execvp
![[Pasted image 20260218093158.png]]

替换自己的程序,用process调mytest
![[Pasted image 20260218093922.png]]
![[Pasted image 20260218094201.png]]

makefile

.PHONY:all
all:myprocess mytest

mytest:mytest.cc
	g++ -o $@ $^ -std=c++11
myprocess:myprocess.c
	gcc -o $@ $^
.PHONY:clean
	rm -f myprocess mytest

![[Pasted image 20260218094613.png]]
![[Pasted image 20260218095255.png]]
![[Pasted image 20260218095435.png]]
![[Pasted image 20260218095604.png]]

调shell脚本
![[Pasted image 20260218141451.png]]
![[Pasted image 20260218143427.png]]
![[Pasted image 20260218143528.png]]

调python文件
![[Pasted image 20260218143929.png]]
![[Pasted image 20260218143952.png]]

所有的语言都是进程,在系统层面,只有代码和数据,只能看到进程

程序替换可以拿到所有的环境变量
![[Pasted image 20260219100752.png]]
![[Pasted image 20260219101109.png]]

![[Pasted image 20260219101434.png]]

结果同上
默认可以通过地址空间继承的方式,让所有的子进程拿到环境变量
进程程序替换,不会替换环境变量数据

  1. 如果想让子进程继承全部的环境变量,直接能拿到
  2. 新增环境变量
    putenv
    ![[Pasted image 20260219105453.png]]

putenv,导到父进程的命令行参数里
新增环境变量不会影响前者,会影响后者
3. 设置全新的环境变量给子进程
execle
![[Pasted image 20260219111229.png]]
![[Pasted image 20260219111254.png]]

直接把父进程的环境变量表传给子进程
![[Pasted image 20260219111609.png]]

简易的shell

考虑下面这个与shell典型的互动:

[root@localhost epoll]# ls  
client.cpp readme.md server.cpp utility.h  
[root@localhost epoll]# ps  
PID TTY TIME CMD  
3451 pts/0 00:00:00 bash  
3514 pts/0 00:00:00 ps

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
![[Pasted image 20260213171622.png]]

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)
    根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了。
    实现代码:
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <fcntl.h>  

#define MAX_CMD 1024  
char command[MAX_CMD];  

int do_face()  
{  
	memset(command, 0x00, MAX_CMD);  
	printf("minishell$ ");  
	fflush(stdout);  
	if (scanf("%[^\n]%*c", command) == 0) {  
		getchar();  
		return -1;  
	}  
	return 0;  
}  

char **do_parse(char *buff)  
{  
	int argc = 0;  
	static char *argv[32];  
	char *ptr = buff;  
	while(*ptr != '\0') {  
		if (!isspace(*ptr)) {  
			argv[argc++] = ptr;  
			while((!isspace(*ptr)) && (*ptr) != '\0') {  
				ptr++;  
			}  
		}else {  
			while(isspace(*ptr)) {  
				*ptr = '\0';  
				ptr++;  
			}  
		}  
	}  
	argv[argc] = NULL;  
	return argv;  
}  

int do_exec(char *buff)  
{  
	char **argv = {NULL};  
	int pid = fork();
	if (pid == 0) {  
		argv = do_parse(buff);  
		if (argv[0] == NULL) {  
			exit(-1);  
		}  
		execvp(argv[0], argv);  
	}else {  
		waitpid(pid, NULL, 0);  
	}  
	return 0;  
}  

int main(int argc, char *argv[])  
{  
	while(1) {  
		if (do_face() < 0)  
			continue;  
		do_exec(command);  
	}  
	return 0;  
}
输出命令行提示符

![[Pasted image 20260219133507.png]]
![[Pasted image 20260219134036.png]]
![[Pasted image 20260219134114.png]]

接收用户输入

![[Pasted image 20260219134754.png]]
![[Pasted image 20260219134832.png]]

scanf默认只能通过空格分隔来获取字符串

fgets,通过指定的文件流获取一行的内容,把获取到的行内容放到缓冲区指针所指向的内容当中,获取成功就返回地址,获取失败就返回NULL
![[Pasted image 20260219141742.png]]
![[Pasted image 20260219141810.png]]

输入的时候敲回车会输入换行,需要去掉换行符
fgets是c函数,默认会给接收的字符串末尾加上\0
![[Pasted image 20260219152632.png]]
![[Pasted image 20260219152806.png]]

封装
![[Pasted image 20260219153023.png]]

对输入的字符串进行切割

strtok根据分隔符,把指定的字符串分割成若干子串
形成的char*数组必须以NULL结尾,方便直接传参
![[Pasted image 20260219154355.png]]
![[Pasted image 20260219154429.png]]

while循环条件用=是因为,当strtok切割失败之后,会返回NULL,正好再一次赋值,把argv数组的最后设置为NULL
然后while循环判断argv[i]为NULL,不符合条件,退出循环

封装
![[Pasted image 20260219155448.png]]
![[Pasted image 20260219155611.png]]

执行命令

![[Pasted image 20260219165621.png]]
![[Pasted image 20260219165739.png]]

封装
![[Pasted image 20260219170304.png]]

shell命令循环使用

![[Pasted image 20260219170725.png]]
![[Pasted image 20260219172652.png]]

处理内建命令

![[Pasted image 20260219174319.png]]

子进程在执行对应的命令,执行cd命令的是子进程,应该让父进程更改路径
![[Pasted image 20260219174822.png]]
![[Pasted image 20260219175212.png]]
![[Pasted image 20260219175314.png]]
![[Pasted image 20260219175348.png]]

直接按回车有空串问题

![[Pasted image 20260219182248.png]]
![[Pasted image 20260219182318.png]]
![[Pasted image 20260219182330.png]]

提示路径的变化

![[Pasted image 20260219184031.png]]
![[Pasted image 20260219184211.png]]

…需要实现
![[Pasted image 20260219184230.png]]

使用getpwd接口
![[Pasted image 20260219234807.png]]
![[Pasted image 20260219234824.png]]

不是获取的…,而是获取的从根目录开始的绝对路径
![[Pasted image 20260220000234.png]]
![[Pasted image 20260220000305.png]]

导入环境变量

子进程执行了export,没有影响到bash进程,需要导入到bash的上下文里
export命令也是内建命令
![[Pasted image 20260220001116.png]]

这样的新建的环境变量,第一次查询是有的,后续通过shell命令查询,又不见了
因为argv指向的是一个字符串,也就是在commandline里,每次循环使用shell,commandline字符串里的内容直接被重新覆盖了,所以导入的环境变量一直在被覆盖
![[Pasted image 20260220080927.png]]
![[Pasted image 20260220084055.png]]

把要导入的环境变量argv拷贝到env里,全局定义的缓冲区里,直接再putenv
这样不会因为命令行进行重置,一直重新覆盖,进而影响环境变量

进程退出码

![[Pasted image 20260220084707.png]]
![[Pasted image 20260220084825.png]]
![[Pasted image 20260220090220.png]]
![[Pasted image 20260220090434.png]]

ls带颜色

![[Pasted image 20260220090934.png]]
![[Pasted image 20260220091006.png]]

函数和进程之间的相似性

exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。
这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间。如下图
![[Pasted image 20260213171956.png]]

一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

Cadence PSpice 仿真11:电感型低通滤波器最坏情况分析仿真实战图文教程 在进行蒙特卡罗分析时会将元器件的容差考虑进去,然后通过运行多次的随机仿真,得到电路输出的一个范围。但是多数情况下设计师关注的只是电路的最坏情况是否满足设计要求。PSpice的最坏情况分析是取在容差的边界点元件参数进行仿真以计算出电路输出的最坏情况。所以如果如果电路中有10个元件的话,需要先取10个元件的标准值仿真1次,然后再分别在10个元件的容差边界处各仿真1次,然后再对仿真结果进行统计和计算,再最坏情况下再仿真1次。 阅读详情

相关推荐

S32K144 LPI2C Master/Slave模式配置与多设备通信实战

本文详细介绍了NXP S32K144微控制器中LPI2C模块的主从模式配置与多设备通信实战。内容涵盖硬件连接、引脚配置、主从模式深度设置、中断/DMA应用,并通过AT24C16 EEPROM、SHT30传感器等实例,解析了多设备通信中的地址切换、时序控制及常见问题排查技巧,帮助开发者高效实现基于LPI2C的稳定通信。

weixin_29158047的博客 269

【Linux】Linux进程控制 --- 进程创建、终止、等待、替换、shell派生子进程的理解…

进程等待给僵尸进程收尸,轮询等待,阻塞和非阻塞等待,7个exec替换函数,shell派生子进程的程序替换,shell的内建命令,cwd和exe路径……

The porters of knowledge 2560

开发者的避坑指南:在Apple Silicon Mac上构建RocketMQ Python客户端的完整心路历程

本文详细记录了在Apple Silicon Mac上构建RocketMQ Python客户端的完整过程,重点解决了架构兼容性问题。通过源码编译rocketmq-client-cpp库,修复构建脚本和CMake配置,最终成功在M2芯片MacBook Pro上部署RocketMQ Python客户端,为开发者提供了实用的避坑指南。

Pepper的专栏 696

【Linux】进程控制&实现自主shell

思考函数和进程之间的相似性exec/exit就像call/return一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux⿎励将这种应用于程序之内的模式扩展到程序之间。如下图一个C程序可以fork/exec另一个程序,并传给它一些参数。

小志的博客 3万+

载波同步技术(一):BPSK的Costas Loop 载波同步

Author:McCrocodile 2010/11/04          载波的提取在相干解调中往往比较重要,根据信号中是否包含载频分量,载波同步一般也可以分为两种方法。包含载频分量的可以采用窄带滤波或者基本的锁相环环路来实现,不包含的则比较麻烦需要采用特殊的环路实现载波的同步。这里介绍的BPSK的载波恢复就属于后面一种。 对于不包含载频分量的信号载波同步一般有:同相-正交环(就是常说的Costas环)、判决反馈环(几乎可以适用所有调制方式)、同相-正交环衍生环(QPSK松尾环、N相同相-正交环)。

McCrocodile Blog 1万+

CANoe进阶实战(第十一章)-LIN节点测试调度

《CANoe快速入门实战:LIN节点调度开发》摘要 本文是CANoe系列教程的第十一章,重点讲解如何通过CAPL编程实现LIN报文的灵活调度控制。文章采用"输入-输出-过程"三步骤法,详细介绍了调度表函数linChangeSchedTable()、启动/停止函数linStartScheduler()/linStopDisturbance()等关键API的使用方法。作者创新性地通过系统变量在Test节点间接控制Network节点的调度功能,并提供了完整的节点代码实现方案。最后的Trace窗

weixin_53605885的博客 1022

从exec到Shell:深度解析Linux进程等待,程序替换与自主Shell实现

本文深入解析Linux进程管理中exec系统调用、程序替换机制及进程等待的原理,通过分析进程创建、替换和回收的全流程,揭示Shell环境下的命令执行本质。文章将结合代码实例演示如何利用fork-exec-wait技术链实现简易Shell。

2402_84532723的博客 1650

电力电子新手必看:COT控制模式的工作原理与典型应用场景解析

本文深入解析了COT控制模式的工作原理及其在电力电子领域的典型应用。COT控制(恒定导通时间控制)通过固定导通时间机制,解决了传统滞环控制的问题,特别适用于需要快速瞬态响应和高效率的应用场景,如CPU/GPU供电和便携设备电源管理。文章详细介绍了COT控制的核心流程、技术特点及设计实践中的关键考量,帮助电力电子新手快速掌握这一技术。

weixin_30745553的博客 522

Linux进程控制

fork函数初识在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程,看如下程序这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after 消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示。

未知陨落的博客 2191

2026 具身智能 VLA/WAM 全栈学习与训练路线:从动作建模、SFT、RL 到世界模型的 RTX PRO 6000 96GB 单卡实战指南

我会把这张表做成“”而不是只列论文名:先核对每个模型截至的公开代码、训练目标和数据接口,再单独给你一套。对于没有公开、只有推理代码或训练细节不完整的模型,我会明确标出来,避免把“论文里提到”误写成“可复现”。目前有一个重要更新:。截至今天 NVIDIA 官方开源主线已经是换成 Cosmos-Reason2-2B/Qwen3-VL 架构,动作端仍是连续路线,而且官方明确给出单卡微调时默认只训练,峰值显存约。所以你的 96GB 单卡对 GR00T SFT 非常宽裕。可以。下面我按,把它整理成一张真正面向“

u013250861的博客 444

手机VPN的连接方法分享

6、返回到VPN页面,可以看到刚刚新添加的VPN,点击开启VPN后面的开关。5、在打开的添加VPN的页面中输入VPN的信息后点击右上角的“保存”按钮。7、待VPN开关开启后,可以看到添加的VPN正在连接的提示信息了。2、在打开的小米手机设置页面中点击“更多连接方式”的菜单项。3、然后在打开的更多连接方式页面中点击VPN的菜单项。4、在打开的VPN设置页面中点击“添加VPN”的按钮。1、首先在手机屏幕左右的滑动,找到手机的设置图标。

惠惠软件 3万+

Linux进程创建,进程终止,进程等待,进程程序替换,简易shell代码

Linux进程创建,进程终止,进程等待,进程程序替换,简易shell代码

weixin_73142957的博客 1180

YOLOv11改进 | 损失函数篇 | YOLOv11引入引MPDIoU, GIoU,DIoU, CIoU, SIoU,EIoU, WIoU, LMPDIoU等损失函数

到此本文的正式分享内容就结束了,在这里给大家推荐我的YOLOv11改进有效涨点专栏,本专栏目前为新开的,后期我会根据各种前沿顶会进行论文复现,也会对一些老的改进机制进行补充,如果大家觉得本文帮助到你了,订阅本专栏,关注后续更多的更新~1. YOLOv11中添加MPDIoU, GIoU,DIoU, CIoU, SIoU等损失函数的方式。文件中用给出的代码替换掉。

tsg6698的博客 5277

【Linux】【实战向】Linux 进程替换避坑指南:从理解 bash 阻塞等待,到亲手实现能执行 ls/cd 的 Shell

本文介绍了进程替换的核心概念和exec系列函数的使用方法。主要内容包括: 解释了为什么sleep命令会"卡住"程序,实际上是bash在等待子进程执行完成。 阐述了进程替换的本质:不创建新进程,只替换当前进程的代码和数据段,保持PID和PCB不变。 分析了exec函数只有失败返回值的原因:成功替换后会覆盖原程序代码,无法返回。 详细介绍了5个exec系列函数: execl:需要完整路径,参数列表形式 execlp:通过PATH查找程序,参数列表形式 execv:需要完整路径,参数数组形式

欢迎来到我的博客,私信我可以一起交流技术 1万+

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

【Linux】进程控制

解析 Linux 中的进程创建、进程终止、进程等待和进程程序替换相关话题,再次理解环境变量!

YoungMLet的博客 1758

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

【Linux】深入理解进程(五)(进程终止,进程等待,进程替换,实现shell)

本文探讨了进程终止与等待的相关机制。首先解释了main函数返回值的作用,0表示成功,非0值表示错误,返回值由父进程接收。介绍了Linux中查看退出码的$?指令,并通过代码示例演示了不同错误情况下的退出码。 详细分析了进程退出的三种场景:正常结束(正确/错误)和异常终止。重点讲解了进程等待的必要性,包括wait和waitpid两种方法:wait会阻塞父进程,waitpid支持非阻塞操作。文章通过代码展示了如何使用这些系统调用来回收子进程资源并获取退出状态。 还介绍了exit和_exit函数的区别,前者会刷新缓

2403_87165176的博客 881

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

一篇文章教会你什么是Linux进程控制

进程创建、进程终止、进程等待、进程替换、自建简易shell

立志做一个高产且细节博主 1409

深入理解 Linux 进程控制

本节重点学习了进程创建(包含 fork/vfork)、进程等待、进程程序替换(通过微型 shell 重新认识 shell 运行原理)以及进程终止(认识 $?)等方面的相关知识。

Edison's 小宇宙 7万+

Linux-进程控制

文章目录进程创建fock函数写时拷贝进程终止进程等待进程程序替换 进程创建 操作系统允许一个进程创建另一个进程,并且允许子进程继承父进程所拥有的资源,当子进程被终止时,其在父进程处继承的资源应当还给父进程。同时,终止父进程同时也会终止其所有子进程。 注意:Linux操作系统对于终止有子进程的父进程,会把子进程交给1号进程接管。 进程创建:1、命令行启动命令(程序、指令等) 2、通过程序自身,fork出子进程 创建进程的过程: 操作系统为新进程分配一个唯一的进程标识号,并申请一个空白的PCB,PCB是有

mmz123_的博客 3058

Linux进程控制:【进程创建】【进程终止】【进程等待】【进程程序替换】【自主shell命令行解释器】

程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间 中!我们知道,进程在被杀死后就会进入僵尸状态,在task_struct内部会记录我们此时的退出状态和信号信息等,父进程进行系统调用(wait)找到子进程,把信息按位操作通过status交到父进程所对应的地址空间上。5.光返回0还不够,我们需要在父进程的外部加一个循环(非阻塞轮询),使得父进程的waitpid隔一段时间就查一下,而隔一段的这个时间,父进程就可以干自己的事情,具体怎么干,参考上面的代码。

2301_81699364的博客 879

[Linux](9)进程控制:进程创建,进程终止,进程等待,进程程序替换

一个程序被加载到内存当中运行,那么内存中的那个数据就被称为进程(process)。进程是操作系统上非常重要的概念,所有系统上面运行的程序都会以进程的形式存在,那么系统的进程有哪些状态?不同的状态会如何影响系统的运行?如何管理进程?这些都是我们所必须要知道的东西。

CegghnnoR的博客 1072

Linux之进程控制

Linux之进程控制

2302_81120572的博客 1762

Linux系统-进程控制

Linux进程控制零、前言一、进程创建1、fork函数2、fork返回值3、fork用法4、fork失败二、进程终止1、退出码2、退出方法1) 调用_exit函数2)调用exit函数3)main函数return4)异常退出3、理解终止三、进程等待1、等待方法2、获取status3、理解等待四、进程替换1、替换原理2、替换方法五、实现简易shell 零、前言 前篇我们讲解学习了关于进程的概念知识,本章主要讲解关于进程的控制,深入学习进程 一、进程创建 1、fork函数 概念: 在linux中fork

~ 2200

【Linux05-进程控制】进程创建、进程等待、进程退出、进程程序替换(附简易shell实现)

进程创建、进程等待、进程退出、进程程序替换(附简易shell实现)

BaconZzz的博客 1412

【linux】进程控制详述

进程控制

qq_66314292的博客 1372

Linux--进程控制

希望这篇linux-进程控制能对你了解操作系统有所帮助

includeevey的博客 4155
上一篇: 7.Linux进程概念|冯诺依曼|操作系统|进程|进程状态|进程优先级|环境变量|程序地址空间|
下一篇: NO.1|扣子|智能体开发|提示词|插件|知识库|数据库
ChoSeitaku
博客等级 码龄5年 3452粉丝 557原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值