Linux 进程 笔记

进程


从Notion拷过来发现好多例图都没了,网盘发pdf

进程的概念

进程的概念 进程是一个独立的可调度的任务 进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源 进程是一个程序的一次执行的过程 进程和程序的区别 程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念 进程是一个动态的概念,它是程序执行的过程,包括创建、调度消亡

进程是程序执行和资源管理的最小单位

总结:程序和进程不是一个概念 程序是死的,就是保存在磁盘上的文件 进程是活的,程序就行起来就是进程,进程是在内存中运行的。

每一个进程在开启或者创建的时候,操作系统都会给其分配4G的虚拟内存,这4G的虚拟内 存分为3G的用户空间(主要用于变量等定义)和1G的内核空间(系统调用),其中用户空 间是当前进程所私有的,内核空间是当前系统中所有进程共有的, 4G的虚拟空间最终会映射到物理内存中,实际使用多少字节的空间,就映射多少

所以我们之前讲解的内存分配,实则是虚拟内存中用户空间的内存划分

进程调度机制

时间片轮转,上下文切换

操作系统将整个将整个内存分为N份,每一份就是一份时间片,每个时间片大约(在Linux上 为5ms-800ms) 上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。上下文切 换过程中的信息被保存在进程控制块(PCB­Process Control Block)中,上下文切换的信 息会一直被保存在CPU的内存中,直到被再次使用。

进程号:主要的进程标识 进程号(Process Identity Number,PID) 父进程号(Parent Process ID,PPID) 进程号是操作系统给运行的进程分配的编号,程序每次运行时理论上编号都不一样

特殊的进程号: 0 内核进程,主要用于管理内核 1 init进程,init进程是所有进程的祖先

ps 查看进程

ps -ajx 详细查看进程

top 动态显示进程

htop 更好查看动态进程

进程的状态

运行状态 系统运行, R

等待状态 等待事件发生 S

停止状态 T ctrl+z 会让进程变为停止态

僵尸状态 进程终止或结束 但在进程表中仍有记录 Z


D: 不可中断的静止,等待 
R: 正在执行中,运行态 
S: 阻塞状态,睡眠态 
T: 暂停执行,停止态 (程序运行时,按下ctrl+z) 
Z: 不存在但暂时无法消除,僵尸态 
W: 没有足够的内存分页可分配 

+: 前台进程 
<: 高优先级的进程 
N: 低优先级的进程 
L: 有内存分页分配并锁在内存中,多线程的 
s: 会话组组长

进程函数Fork

#include <sys/types.h> 
#include <unistd.h> 
pid_t fork(void); 
功能:当前进程创建一个子进程
参数:无 
返回值:
成功: 
>0 在父进程里面返回子进程的进程号 
0 在子进程里面返回0 
失败:‐1

例子:


pid_t pid = fork(); 
if(pid ==1) 
{ 
	perror("fork error"); 
	return1; 
} 
else if(pid > 0) //父进程的代码区 
{ 
	printf("父进程正在运行\n"); 
	sleep(1); 
	printf("hahahahahaha\n"); 
} 
else //子进程的代码区 
{ 
	printf("子进程正在运行\n");
	sleep(1);  
	printf("hehehehehehehe\n");  
}  
printf("hello world\n");

获取进程号和父进程号 getpid()/getppid()

获取当前进程的进程号 
pid_t getpid(void); 
获取当前进程的父进程号 
pid_t getppid(void);

其它操作

小tips :exit()和_exit()

void exit(int status);

功能:结束一个进程,是一个库函数

参数:

status:当前进程的退出状态,父进程可以通过wait函数获取

返回值:无

#include <unistd.h> 
void _exit(int status); 
功能:结束一个进程,是一个系统调用 
参数: 
status:当前进程的退出状态,父进程可以通过wait函数获取 
返回值:无

wait()/waitpid()

#include <sys/types.h> 
#include <sys/wait.h> 
pid_t wait(int *wstatus); 
功能:阻塞等待子进程的退出状态
参数: 
wstatus:子进程的退出状态,子进程使用exit之后wait才可以接收到 
一般情况传NULL即可,表示不接收子进程的退出状态 
返回值: 
成功:退出的子进程的进程号 
失败:‐1 
pid_t waitpid(pid_t pid, int *wstatus, int options); 
功能:阻塞等待子进程的退出状态 
参数: 
pid:指定接收的子进程的状态 
<1 接收这个参数的绝对值的进程组中所有的子进程的退出 
‐1 接收所有的子进程的退出(常用) 
0 接收进程组号刚好是当前进程的组中子进程的退出 
>0 接收指定进程号为当前参数的子进程的退出 
status:子进程的退出状态,子进程使用exit之后wait才可以接收到 
一般情况传NULL即可,表示不接收子进程的退出状态 
options:标志位 
0 阻塞
WNOHANG 非阻塞 
返回值: 
成功:退出的子进程的进程号 
失败:‐1 
wait(NULL) <==> waitpid(1, NULL, 0)

waitpid(‐1, NULL, 0);

处理僵尸进程的方式: 第一种:将父进程结束,父进程退出,僵尸进程会变成孤儿进程,孤儿进程会有init回 收资源第二种:使用wait函数,这个函数是父进程要阻塞等待子进程退出,当子进程退出之后 可以处理僵尸进程
第三种:使用信号,通过异步解决僵尸进程的问题,需要结合wait或者waitpid一起使用。

孤儿进程 orphan 父进程结束,子进程没有结束,此时子进程会变成孤儿进程, 孤儿进程将来退出之后,会由init进程回收资源 孤儿进程的父进程就是1号进程,也就是init进程 由于孤儿进程的父进程是init进程,所以不受当前终端影响

Alarm函数 设定一定的时间,代码继续运行,当时间到达时会产生SIGALRM信号


#include <unistd.h> 
unsigned int alarm(unsigned int seconds); 
功能:设定一定的时间,代码继续运行,当时间到达时会产生SIGALRM信号, 
默认对进程的处理方式是终止进程 
参数: 
seconds:设定的秒数 
返回值: 
成功:上一个alarm剩余的时间 
失败:0

注意:
alarm的返回值表示上一个alarm剩余的时间 
如果一个程序中有很多alarm,后面的alarm会覆盖之前的alarm,之前的都会失效

#include <unistd.h> 
int pause(void); 
功能:阻塞等待一个信号产生 
参数:没有参数 
返回值: 
成功:信号 
失败:‐1

例题

1.求打印了多少个A

int main()
{
		for(int i=0;i<2;i++)
		{
			fork();
			printf("A\n");
		}
}

2.当printf去掉\n呢

int main()
{
		for(int i=0;i<2;i++)
		{
			fork();
			printf("A");
		}
}

3.打印了几个A

int main()
{
	fork() || fork();
	printf("A");
}

答案 :1 打印了6个A

如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fu3AX5VV-1665491677478)(%E8%BF%9B%E7%A8%8B%206c7341e2eea644dc9cfe511b5f370662/Untitled%203.png)]

printf 遇到、n打印并清空缓冲区,打印六个A

  2.打印8个A

如图

而没有、n,printf的缓冲区没有完整清除,到值打印了4个AA

3.打印了3个A

父进程fork的返回值是大于0的,所以第二个fork不执行,而子进程中fork的返回值是0,要执行第二个,fork。输出一个A和一个AA

进程间的通信

传统的进程间通信方式

1.无名管道(pipe)

管道(Pipe):管道是一种半双工的通信方式,数据只能单向流动,而且 只能在具有亲缘关系的进程间使用,允许一个进程和另一个与它有共同祖先的进 程之间进行通信。

只能用于具有亲缘关系的进程之间的通信 半双工的通信模式,具有固定的读端和写端
管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。 管道是基于文件描述符的通信方式。

当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]其中fd[0]固定用于读管道,而 fd[1]固定用于写管道。

int pipe(int pipefd[2]); 
功能:创建一个无名管道 
参数: 
pipefd:开辟内核空间后的两个文件描述符 
pipefd[0] 读操作 
pipefd[1] 写操作 
返回值: 
成功:0 
失败:‐1

例子:

//使用pipe函数创建无名管道 
//当从管道中读取数据时,原本管道中读取的数据会被移除 
 //如果管道中没有数据,则读操作会阻塞等待 
 int fd[2]//创建int 数组存储两个文件描述符

if(pipe(fd) ==1) 
 { 
		 perror("pipe error");
		 exit(1); 
 }

//向无名管道中写数据 
 write(fd[1], "hello world", 11); //写入hello world
 write(fd[1], "nihao beijing", 13);//写入nihao beijing

注意:无名管道无法修改管道内容的位置
即不能使用lseek等,

//从管道中读取数据 
 char buf[32] = {0}; 

 read(fd[0], buf, 10); //读
 printf("buf = %s\n", buf); //打印

 read(fd[0], buf, 10); //读
 printf("buf = %s\n", buf);//打印

log: 
buf = hello worl
buf = dnihao bei

附:无名管道的读写规律

1 、读写端都存在,只读不写

write(fd[1], "hello world", 11); 
 //如果读写端都存在,只读不写 
 //如果管道中有数据,则正常读取,如果管道中没有数据,则读端阻塞等
char buf[32] = {0}; 
read(fd[0], buf, 32);                   //阻塞
printf("buf = %s\n", buf); 
read(fd[0], buf, 32); 
printf("buf = %s\n", buf);

2、 读写段都存在,只写不读

//如果读写端都存在,只写不读 
 //当管道写满后,写操作就会发生阻塞,测试得到**无名管道64k字节** 
 int num = 0; 
 while(1) 
 { 
	 write(fd[1], "hello", 1024); 
	 num++; 
	 printf("num = %d\n", num); 
 }

3、只有读端,没有写端

write(fd[1], "hello world", 11); 
 //只有读端没有写端 
 //如果管道中有数据,则正常读取,如果没有数据,读端会返回0 
 close(fd[1])//**将写端关闭的方法**

read(fd[0], buf, 32);  当取完了就返回0

4、只有写端没有读端

close(fd[0]);//**将读端关闭的方法

//只有写端没有读端
//此时当执行写操作时,进程会结束,因为产生一个信号SIGPIPE(管道破裂),
 //这个信号默认对进程的处理方式是结束进程**

2.有名管道(fifo) (命名管道)

命名管道(named pipe):也是半双工的通信方式,除具有管道所具有 的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的 文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。

有名管道可以使互不相关的两个进程互相通信。

有名管道可以通过路径名来指出,并且在文 件系统中可见 p表示管道文件

进程通过文件IO来操作有名管道

有名管道遵循先进先出规则 不支持如lseek() 操作

创建有名管道的方法: 
使用shell命令: 
mkfifo 文件名 
使用mkfifo函数 

#include <sys/types.h> 
#include <sys/stat.h> 
int mkfifo(const char *pathname, mode_t mode); 
功能:创建一个有名管道 
参数:
pathname:管道文件名 
mode:管道文件的权限,一般设置为**0664** 
返回值: 
成功:0 
失败:‐1

例子:

if(mkfifo("myfifo", 0664) ==1) 
{ 
	//如果是因为管道文件存在所报的错,则无需退出进程 
	//printf("errno = %d\n", errno); 
		if(errno != 17) 
		{ 
			ERRLOG("mkfifo error"); 
		} 
}
else 
{ 
		printf("fifo file has created successfully\n"); 
}

int fd; 
if((fd = open("myfifo", O_RDWR)) ==1) //打开有名管道且可读可写方式
{ 
	 ERRLOG("open error"); 
}
**向有名管道中写入数据 
注意:有名管道产生的文件只是起到标识作用,方便不同的进程可以找到, 
但是有名管道的读写操作本质还是对内核空间开辟的区域的操作,跟这个文件没有关系 

有名管道的操作基本跟无名管道类似,读取到的数据会从管道中移除,
如果管道中没有数据,读操作会阻塞。**

write(fd, "hello world", 11); //阻塞(假设管道没有写入数据)
write(fd, "nihao beijing", 13);
****

附:有名管道读写规律

1、读写端都存在,只读不写

//有名管道读写端都存在,只读不写 
//如果管道中有数据,则正常读取,如果没有数据,则阻塞等待 
int fd; 
if((fd = open("myfifo", O_RDWR)) ==1) 
{ 
		ERRLOG("open error"); 
}
write(fd, "hello world", 11);//没有数据则等待

2、读写端都存在,只写不读

有名管道读写端都存在,只写不读 
当管道写满后,写操作会发生阻塞,**默认有名管道64K**

与无名管道一样

3、**只有读端没有写端 *******

if((fd = open("myfifo", O_RDONLY)) ==1)//**只读达到只有读端的状态//(堵塞)**
{ 
	ERRLOG("open error"); 
}

**有名管道只有读端没有写端 
会在open函数的位置发生阻塞**

4、只有写端没有读端

有名管道之后写端没有读端 
会在open函数阻塞

if((fd = open("myfifo", O_WRONLY)) ==1) //**只写达到只有写端  // (堵塞)**
{ 
	ERRLOG("open error"); 
}

例子*

读写端:

***//  recv.c  接收端***
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <fcntl.h> 
#include <errno.h> 
#include <string.h> 

#define ERRLOG(errmsg) do{perror(errmsg);exit(1);}while(0) 
int main(int argc, char const *argv[]) 
{ 
		if(mkfifo("myfifo", 0664) ==1) 
		{ 
				if(errno != 17) 
				{ 
						ERRLOG("mkfifo error"); 
				} 
		} 
		else 
		{ 
				printf("fifo file has created successfully\n"); 
		} 
		**//如果一个进程只读,另一个进程只写 
		//通信过程中如果关闭写端,读端返回0** 
		int fd; 
		if((fd = open("myfifo", O_RDONLY)) ==1) 
		{ 
				ERRLOG("open error"); 
		}
		
		char buf[32] = {0}; 
		ssize_t bytes; 
		while(1) 
		{ 
				if((bytes = read(fd, buf, 32)) ==1) 
				{ 
						ERRLOG("read error"); 
				} 
				printf("bytes = %ld, buf = %s\n", bytes, buf); 
				memset(buf, 0, sizeof(buf)); 
		} 
		return 0; 
}
//  ***send.c  发送端*** 
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <fcntl.h> 
#include <errno.h> 
#include <string.h> 
#define ERRLOG(errmsg) do{perror(errmsg);exit(1);}while(0) 

int main(int argc, char const *argv[]) 
{ 
		if(mkfifo("myfifo", 0664) ==1) 
		{ 
				if(errno != 17) 
				{
						ERRLOG("mkfifo error"); 
				} 
		} 
		else 
		{ 
				printf("fifo file has created successfully\n"); 
		} 
		//**如果一个进程只读,另一个进程只写** 
		//**通信过程中如果关闭读端,写端会产生SIGPIPE信号,是的当前进程结束** 
		int fd; 
		if((fd = open("myfifo", O_WRONLY)) ==1) 
		{ 
				ERRLOG("open error"); 
		} 
		
		char buf[32] = {0}; 
		while(1) 
		{ 
				fgets(buf, sizeof(buf), stdin); 
				buf[strlen(buf)1] = '\0';  
				if(write(fd, buf, sizeof(buf)) ==1) 
				{ 
						ERRLOG("write error");
				} 
		} 
		return 0;
}

3.信号(signal)

信号(Signal):信号是一种比较复杂的通信方式,用于通知接收进程某 个事件已经发生。是Unix系统中使用的最古老的进程间通信的方法之一。

操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一 个),它也是用户进程之间通信和同步的一种原始机制。

每一个信号产生之后对进程都有一个默认的处理方式,但是不同的信号对进程的处理是不一 样的,所有的信号对进程的处理方式分为四类

1、让进程结束

2、让进程变为停止态

3、让停止的进程恢复执行(后台进程)

4、忽略,对当前进程不做任何处理

用户可以对产生信号之后的进程作出其他处理方式,

1、缺省,默认方式处理

2、忽略,当信号产生后,对当前进程不做任何处理

3、捕捉,自定义方式处理,一般需要通过编写信号处理函数 注意:SIGKILL和SIGSTOP这两个信号只能按照默认的方式处理

linux 查询当前系统中已经定义的信号

signal() 

#include <signal.h> 
typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler); 
功能:当信号产生后设置进程的处理方式 
参数: 
signum:信号 
handler:信号的处理方式 
SIG_DFL 以默认的方式处理 
SIG_IGN 以忽略的方式处理 
handler 以捕捉的方式,也就是通过信号处理函数执行 
void handler(int sig) 
{ 
} 
返回值: 
成功:当前信号原本默认的处理方式 
失败:SIG_ERR

例子:

void handler(int sig) 
{ 
		if(sig == SIGINT) 
		{ 
				printf("SIGINT信号产生了,正在执行信号处理函数\n"); 
		} 
		if(sig == SIGQUIT) 
		{ 
				printf("SIGQUIT信号产生了,正在执行信号处理函数\n"); 
		} 
		if(sig == SIGTSTP) 
		{
				printf("SIGTSTP信号产生了,正在执行信号处理函数\n"); 
		} 
}

主函数内
signal(SIGINT, handler);
signal(SIGQUIT, handler);

System VIPC对象

1.共享内存(share memory) *****

共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享 内存由一个进程创建,但多个进程都可以访问,是最快的可用IPC形式。是针对 其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使 用,来达到进程间的同步及互斥。

IPC对象就是AT&T公司根据最原始的进程间通信方式改造过来的另外三种进程间通信方 式:消息队列、共享内存、信号灯集 使用ipcs查看当前系统中所有的ipc对象

ipcs 查看所有的ipc对象

ipcs -q 查看消息队列

ipcs -m 查看共享内存

ipcs -s 查看信号量

ipcrm 删除ipc对象

ipcrm –q msqid 删除指定的消息队列

共享内存是一种最为高效的进程间通信方式,进程可以直接读写物理内存,而不的拷贝需要 任何数据 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射 到自己的私有地址空间 进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。

由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

共享内存是所有进程间通信方式里面效率最高的,直接对物理内存进行操作。

打开或创建一个共享内存 shmget()

#include <sys/ipc.h> 
#include <sys/shm.h> 
int shmget(key_t key, size_t size, int shmflg); 
功能:**打开或者创建一个共享内存** 
参数: 
key:键值 
size:**共享内存的大小** 
shmflg:**权限** 
一般设置为 **IPC_CREAT | 0777** 
返回值: 
成功:**共享内存id** 
失败:‐1

控制共享内存 shmctl()

#include <sys/ipc.h> 
#include <sys/shm.h> 
int shmctl(int shmid, int cmd, struct shmid_ds *buf); 
功能:**控制共享内存** 
参数:
shmid:**共享内存id** 
cmd:执行命令 
**IPC_STAT 获取共享内存信息 
IPC_SET 设置共享内存信息 
IPC_RMID 删除共享内存** 
buf:**共享内存信息结构体** 
返回值: 
成功:0 
失败:‐1

例子:

//**使用ftok函数获取键值**
key_t key; 
if((key = ftok(".", 100)) ==1) 
{ 
		perror("ftok error"); 
		exit(1); 
}

//**创建一个共享内存** 
int shmid; 
if((shmid = shmget(key, 500, IPC_CREAT |0777)) ==1) 
{ 
		perror("shmget error"); 
		exit(1); 
}

//**删除共享内存** 
if(shmctl(shmid, IPC_RMID, NULL) ==1) 
{ 
	perror("shmctl error"); 
	exit(1); 
} 
system("ipcs ‐m");

映射共享内存的地址 shmat()

#include <sys/types.h> 
#include <sys/shm.h> 
void *shmat(int shmid, const void *shmaddr, int shmflg); 
功能:**映射共享内存的地址** 
参数: 
shmid:共享内存id 
shmaddr:映射的地址,一般设置为NULL表示自动映射 
shmflg:标志位 
0 可读可写 
SHM_RDONLY 只读 
返回值: 
成功:映射后给用户的地址 
失败:‐1

取消共享内存映射 shmdt

int shmdt(const void *shmaddr); 
功能:取消共享内存映射 
参数: 
shmaddr:要取消的映射的地址,shmat的返回值 
返回值: 
成功:0 
失败:‐1

例子:

send.c

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <string.h> 
int main(int argc, char const *argv[]) 
{ 
	//使用ftok函数获取键值 
	key_t key;
	if((key = ftok(".", 100)) ==1) 
	{ 
		perror("ftok error"); 
		exit(1); 
	} 
	//创建一个共享内存 
	int shmid; 
	if((shmid = shmget(key, 500, IPC_CREAT |0777)) ==1) 
	{ 
		perror("shmget error"); 
		exit(1); 24 
  } 
	printf("shmid = %d\n", shmid); 
	system("ipcs ‐m"); 
	//映射共享内存 
	char *buf = shmat(shmid, NULL, 0); 
	strcpy(buf, "hello world"); //你需要拿着给你的地址,向里面写入你想要的数据

	//取消共享内存映射 
	shmdt(buf);
	//删除共享内存 
	// if(shmctl(shmid, IPC_RMID, NULL) == ‐1) 
	// { 
	// perror("shmctl error"); 
	// exit(1); 
	// } 
	system("ipcs ‐m"); 
	return 0; 
}

recv.c

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <string.h> 
int main(int argc, char const *argv[]) 
{ 
	//使用ftok函数获取键值 
	key_t key; 
	if((key = ftok(".", 100)) ==1) 
	{ 
		perror("ftok error"); 
		exit(1); 
	} 
	
	//创建一个共享内存 
	int shmid; 
	if((shmid = shmget(key, 500, IPC_CREAT |0777)) ==1) 
	{ 
		perror("shmget error");
		exit(1);
	} 
	printf("shmid = %d\n", shmid); 
	system("ipcs ‐m"); 
	//映射共享内存 
	char *buf = (char *)shmat(shmid, NULL, 0); //读出该地址的数据,转为char*类型,
	printf("buf = %s\n", buf);                //打印字符串出来
	//取消共享内存映射 
	shmdt(buf); 
	//删除共享内存 
	// if(shmctl(shmid, IPC_RMID, NULL) == ‐1) 
	// { 
	// perror("shmctl error"); 
	// exit(1); 
	// } 
	system("ipcs ‐m"); 
	return 0; 
}

2.消息队列(message queue) *****

消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列 system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限 的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能 承载无格式字节流以及缓冲区大小受限等缺。

消息队列是IPC对象的一种 消息队列由消息队列ID来唯一标识 消息队列就是一个消息的列表。

用户可以在消息队列中添加消息、读取消息等。

消息队列可以按照类型来发送/接收消息

创建或打开消息队列 msgget()

msgget() / msgctrl() / ftok() 
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/msg.h>

int msgget(key_t key, int msgflg); 
功能:**创建或者打开一个消息队列** 
参数: 
key:键值,通过唯一的键值可以得到唯一的消息队列 
key的设置: 
第一种:随意设置 
第二种:ftok函数获取键值 
msgflg:标志位 
通常设置为 IPC_CREAT | **0777** 
返回值: 
成功:消息队列id 
失败:‐1 

获取消息队列键值 ftok()

#include <sys/types.h> 
#include <sys/ipc.h> 
key_t ftok(const char *pathname, int proj_id); 
功能:获取键值 
参数: 
pathname:路径 
proj_id:任意一个值,一般为0127 
返回值: 
成功:键值 
失败:‐1 

设置消息队列属性 msgctl()

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
int msgctl(int msqid, int cmd, struct msqid_ds *buf); 
功能:**设置消息队列的属性** 
参数: 
msqid:指定消息队列的id 
cmd:执行命令 
IPC_STAT 获取消息队列属性信息

IPC_SET 设置消息队列属性信息 
IPC_RMID 删除消息队列 
msqid_ds:保存或者设置消息队列属性结构体 
返回值: 
成功:0 
时报:‐1

向消息队列里写内容 msgsnd()

#include <sys/types.h>
#include <sys/ipc.h> 
#include <sys/msg.h> 
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 
功能:向消息队列写内容 
参数: 
msqid:**消息队列id** 
msgp**:要写的内容,必须自己定义一个结构体** 
struct msgbuf { 
long mtype; //消息类型 
char mtext[1]; //消息内容 
}; 
msgsz:消息内容的大小,**不包括消息类型** 
msgflg:标志位 
0 阻塞 
IPC_NOWAIT 非阻塞 
返回值: 
成功:0 
失败:‐1

向消息队列里读内容 msgrcv()

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, 
long msgtyp, int msgflg); 
功能:从消息队列中读取数据 
参数: 
msqid:消息队列id 
msgp:保存接收的内容 
msgsz:消息内容的大小,**不包括消息类型** 
msgtyp:指定读取内容的类型 
0 按照顺序读取第一个内容 
>0 读取第一个消息类型为当前参数的消息 
<0 读取小于等于这个数的绝对值中最小消息类型的消息 

例如:‐3 读取<=3**最小的**
msgflg:标志位 
0 阻塞 
IPC_NOWAIT 非阻塞 
返回值: 
成功:接收的消息内容的大小 
失败:‐1

例子:

//Send.c 

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
#include <unistd.h> 
typedef struct{ 
long type; 
int id; 
char name[32]; 
int score; 
}MSG; 
#define TEXTLEN (sizeof(MSG)sizeof(long)) 
int main(int argc, char const *argv[]) 
{ 
//获取键值 
key_t key; 
if((key = ftok(".", 100)) ==1) 
{ 
	perror("ftok error"); 
	exit(1); 
} 
//创建或者打开一个消息队列 
int msqid;
if((msqid = msgget(key, IPC_CREAT | 0777)) ==1) 
{ 
		perror("msgget error"); 
		exit(1); 
} 
system("clear"); 
printf("msqid = %d\n", msqid); 
system("ipcs ‐q"); 
//使用msgsnd向消息队列发送数据 
MSG msg1 = {1, 1001, "张三", 90}; 
MSG msg2 = {2, 1002, "李四", 100}; 
MSG msg3 = {3, 1003, "王五", 80}; 
MSG msg4 = {4, 1004, "赵六", 95};

if(msgsnd(msqid, &msg1, TEXTLEN, 0) ==1)
{ 
		perror("msgsnd error"); 
		exit(1); 
}
if(msgsnd(msqid, &msg2, TEXTLEN, 0) ==1)
{ 
		perror("msgsnd error"); 
		exit(1); 
}
if(msgsnd(msqid, &msg3, TEXTLEN, 0) ==1)
{ 
		perror("msgsnd error"); 
		exit(1); 
}
if(msgsnd(msqid, &msg4, TEXTLEN, 0) ==1)
{ 
		perror("msgsnd error"); 
		exit(1); 
}
//删除消息队列 
#if 0 
if(msgctl(msqid, IPC_RMID, NULL) ==1) 
{ 
	perror("msgctl error"); 
	exit(1); 
} 
#endif 
system("ipcs ‐q"); 
return 0;
}
//recv.c 
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
#include <unistd.h> 
typedef struct{ 
long type; 
int id; 
char name[32]; 
int score; 
}MSG; 
#define TEXTLEN (sizeof(MSG)sizeof(long)) 
int main(int argc, char const *argv[]) 
{ 
//获取键值 
key_t key; 
if((key = ftok(".", 100)) ==1)
{ 
		perror("ftok error"); 
		exit(1); 
} 
//创建或者打开一个消息队列 
int msqid; 
if((msqid = msgget(key, IPC_CREAT | 0777)) ==1) 
{ 
		perror("msgget error"); 
		exit(1);
} 
system("clear"); 
printf("msqid = %d\n", msqid); 
system("ipcs ‐q"); 
//使用msgsnd向消息队列发送数据 
MSG msg;
if(msgrcv(msqid, &msg, TEXTLEN,3, 0) ==1) 
{ 
	perror("msgrcv error"); 
	exit(1); 
} 
printf("%d‐%s‐%d\n", msg.id, msg.name, msg.score); 
//删除消息队列 
#if 0 
if(msgctl(msqid, IPC_RMID, NULL) ==1) 
{ 
		perror("msgctl error"); 
		exit(1); 
} 
#endif 
system("ipcs ‐q");

return 0; 
}

如果允许,可以使用结构体数组传输。

MSG msgs[]={{1, 1001, "张三", 90},{2, 1002, "李四", 100},{3, 1003, "王五", 80},{4, 1004, "赵六", 95}};

for (int i = 0; i < 4; i++)
{
    msgsnd(msqid, &msgs[i], TEXTLEN, 0);
}

另外需要注意,如果读取之后则从消息队列中消失,未读取的不会消失,依然存在。

3.信号灯(semaphore) (信号量)

信号量(semaphore):信号量是一个计数器,可以用来控制多个进程对 共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他 进程也访问该资源。

因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

信号灯集就是信号量,只不过可以通过一个函数创建多个信号量 具体操作还是PV操作,P操作是减操作,V操作是加操作,当信号量的值为0时,P操作阻塞

但实际中使用多个信号也不会使用信号灯集。

打开或创建一个信号灯集 semget( )

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
int semget(key_t key, int nsems, int semflg);
功能:打开或者创建一个信号灯集 
参数: 
key:键值 
nsems:信号量的个数,至少1个 
semflg:标志位 
一般设置为 IPC_CREAT | 0777 
返回值: 
成功:信号灯集的id 
失败:‐1

控制信号灯集 semctl()

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
int semctl(int semid, int semnum, int cmd, ...); 
功能:控制信号灯集 
参数: 
semid:信号灯集的id 
semnum:要操作的信号的编号,从0开始 
cmd:执行命令 
IPC_RMID 删除信号灯集 
SETVAL 设置信号量的初始值 
需要通过第四个参数来设置, 
需要自己定义公用体 
union semun
{ 
	int val; 
} 
返回值: 
成功:0 
失败:‐1

执行PV操作 semop()

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops); 
功能:执行PV操作 
参数: 
semid:信号灯集的id 
sops:结构体指针数组,实现具体PV操作 
struct sembuf 
{ 
	unsigned short sem_num; 要操作的信号量的编号,从0开始 
	short sem_op; 
	PV操作 
	>0 V操作 
	<0 P操作 
	short sem_flg; 标志位 
	0 阻塞 
	IPC_NOWAIT 非阻塞 
} 
nsops:要操作的信号量的个数 
返回值: 
成功: 
失败:

例子:

//获取键值
key_t key; 
if((key = ftok(".", 100)) ==1) 
{ 
	perror("ftok error"); 
	exit(1); 
}

//创建信号灯集 
int semid; 
if((semid = semget(key, 3, IPC_CREAT | 0777)) ==1) 
{ 
	perror("setget error"); 
	exit(1); 
}

//删除信号灯集 
if(semctl(semid, 0, IPC_RMID) ==1) 
{ 
		perror("semctl error"); 
		exit(1); 
} 
 system("ipcs ‐s")//查看

BSD

1.套接字(socket)

套接字(Socket):与其他通信机制不同的是,它可用于不同机器间的进 程通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其 它类Unix系统上:Linux和System V的变种都支持套接字。

进程间的通信——总结:

    进程间如何实现通信? 

因为任何一个进程创建或者开启的时候,都会有公有的1G的内核空间,所以多个进程 只要能够操作同一个在内核空间开辟的区域,就可以实现通信 所以学习进程间通信方式其实就是学习如何在内核空间开辟区域(除信号和共享内存)。

    **前六种进程间通信方式只能实现一台主机的多个进程间通信 只有套接字可以在不同主机的多个进程间实现通信 效率最高的:共享内存,他是直接获取到物理内存的地址,直接对物理内存进行操作。**

总结一些函数和指令等

IO 
	标准IO 
	文件指针(FILE *fopen() fclose() 
	fprintf() sprintf() 
	fgetc() fpuc() fgets() fputs() fread() fwrite() 
	fseek() rewind() ftell() 
	perror() errno

文件IO 
	文件描述符(int) 
	open() close() 
	read() write() 
	lseek() 
	文件属性 
	stat 
	目录操作 
	opendir() 
	readdir() 
	closedir() 
	动态库和静态库 
	静态库 
	gcc ‐c mymath.c ‐o mymath.o 
	ar crs libmymath.a mymath.o 
	gcc main.c ‐L. ‐lmymath 
	动态库 
	gcc ‐fPIC ‐c mymath.c ‐o mymath.o 
	gcc ‐shared mymath.o ‐o libmymath.so 
	gcc main.c ‐lmymath 
进程 
	进程的基本操作 
	fork() 
	getpid() getppid() 
	exit() _exit() 
	wait() waitpid() 
	进程间通信方式 
	原始的: 
	无名管道 
	pipe() 
	有名管道 
	mkfifo() 
	信号通信 
	signal() 
	kill() raise()
	alarm() pause() 
	IPC对象: 
	消息队列 
	ftok() 
	msgget() 
	msgctl() 
	msgsnd() 
	msgrcv() 
	共享内存 
	shmget() 
	shmctl() 
	shmat() 
	shmdt() 
	信号灯集 
	semget() 
	semctl() 
	semop() 
线程 
	线程的基本操作 
	pthread_create() 
	pthread_self() 
	pthread_exit() 
	pthread_cancel() 
	pthread_join() 
	pthread‐detach() 
	线程间同步与互斥机制 
	互斥锁 
	pthread_mutex_t
	pthread_mutex_init() 
	pthread_mutex_lock() 
	pthread_mutex_unlock() 
	pthread_mutex_destroy(); 
	条件变量 
	pthread_cond_t 
	pthread_cond_init()
	pthread_cond_signal()
	pthread_cond_broadcast()
	pthread_cond_wait() 
	pthread_cond_destroy() 
	信号量 
	sem_t 
	sem_int() 
	sem_post() 
	sem_wait() 
	sem_destroy()
                                                                                                点赞获取PDF版本

链接: https://pan.baidu.com/s/1DdnO4k3Y3A9q_kyVUOUJrw?pwd=yyds
提取码: yyds
复制这段内容后打开百度网盘手机App,操作更方便哦

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
采用当前最流行的系统设计理念,实现界面与程序100%分离,更换界面轻松自如。首页、栏目页、文档页均生成HTML静态页。大大节省服务器资源消耗,提升页面请求响应速度。 注重搜索引擎优化(SEO)。允许自主设置keywords、description信息,还可以针对每一篇文档单独设置。支持生成站地图HTML页面。 该系统具有以下功能亮点: 1、后台管理菜单可自由增删,可以给每个管理帐号指定页面维护权限。 2、除了页面维护权限外,还增加了栏目的维护权限分配。灵活实现分工维护。 3、提供系统设置,可设置一些通用的信息。比如站名称、数据库名称、允许的上传格式、模板文件指定等等。 4、引入模型概念。通过选用模型创建站栏目。栏目继承模型所有属性、功能。目前系统拥用5大块模型。包括:单页模型、文章模型、图集模型、招聘模型、反馈模型。还支持模型的自由安装、卸载。后台可直接操作。 5、融入插件概念。通过插件辅助,进一步扩展系统功能。系统当前默认安装5个插件。包括:幻灯片插件、投票插件、链接插件、广告插件、公告插件。插件同样支持自由安装、卸载。 6、采用当前最流行的系统设计理念,实现界面与程序100%分离,更换界面轻松自如。 7、拥有强大的在线模板管理器。其中包括创建、编辑、删除、复制、上传文件、创建目录,还支持指定范围内的字符替换。 8、拥用强劲的上传附件管理器。除了基本的文件更名、删除、预览,还可以在线创建目录,在任意子目录内上传文件,并且可以设定上传文件(图片类型)的尺寸裁剪。 9、首页、栏目页、文档页均生成HTML静态页。大大节省服务器资源消耗,提升页面请求响应速度。 10、注重搜索引擎优化(SEO)。允许自主设置keywords、description信息,还可以针对每一篇文档单独设置。支持生成站地图HTML页面。 更多功能等待您来发现。。。。。。 默认后台管理帐号密码: 帐号:admin 密码:123456 以下是关于根目录下各个文件夹和文件的介绍说明: data ====== 数据库目录 gn ====== 后台管理目录 html ====== 存放html生成文件 include ====== 程序包含文件 plus ====== 额外功能执行文件 template ====== 模板文件(站外观文件) uploadfile ====== 上传文件存放处 index.htm ------ 首页文件(由后台生成) robots.txt ------ 供搜索引擎蜘蛛抓爬的参照文件 该新版本较1.0做了哪些改动: 1、模板不再基于数据库。脱离了数据库的束缚,使模板操作更加方便,比如可自由创建新模板页,删除或更改模板名等。调用模板也大大简单化。 2、完善了模板管理面板的功能。 3、对“图集模型”中的图片输出形式功能进行更加人性化的功能完善。比如上传的图片如果比自定义的缩略图小时,就直接用上传图作为缩图而不另外生成缩图。 4、增加生成首页、站地图的模板文件路径的自定义设置。 5、修改了模板标签的解析内核。生成静态时避免了1.0资源消耗过大的问题。
无名站管理系统nameless CMS 具有以下功能亮点: 1、后台管理菜单可自由增删,可以给每个管理帐号指定页面维护权限。 2、除了页面维护权限外,还增加了栏目的维护权限分配。灵活实现分工维护。 3、提供系统设置,可设置一些通用的信息。比如站名称、数据库名称、允许的上传格式、模板文件指定等等。 4、引入模型概念。通过选用模型创建站栏目。栏目继承模型所有属性、功能。目前系统拥用5大块模型。包括:单页模型、文章模型、图集模型、招聘模型、反馈模型。还支持模型的自由安装、卸载。后台可直接操作。 5、融入插件概念。通过插件辅助,进一步扩展系统功能。系统当前默认安装5个插件。包括:幻灯片插件、投票插件、链接插件、广告插件、公告插件。插件同样支持自由安装、卸载。 6、采用当前最流行的系统设计理念,实现界面与程序100%分离,更换界面轻松自如。 7、拥有强大的在线模板管理器。其中包括创建、编辑、删除、复制、上传文件、创建目录,还支持指定范围内的字符替换。 8、拥用强劲的上传附件管理器。除了基本的文件更名、删除、预览,还可以在线创建目录,在任意子目录内上传文件,并且可以设定上传文件(图片类型)的尺寸裁剪。 9、首页、栏目页、文档页均生成HTML静态页。大大节省服务器资源消耗,提升页面请求响应速度。 10、注重搜索引擎优化(SEO)。允许自主设置keywords、description信息,还可以针对每一篇文档单独设置。支持生成站地图HTML页面。 无名站管理系统nameless CMS v1.1较1.0做了哪些改动: 1、模板不再基于数据库。脱离了数据库的束缚,使模板操作更加方便,比如可自由创建新模板页,删除或更改模板名等。调用模板也大大简单化。 2、完善了模板管理面板的功能。 3、对“图集模型”中的图片输出形式功能进行更加人性化的功能完善。比如上传的图片如果比自定义的缩略图小时,就直接用上传图作为缩图而不另外生成缩图。 4、增加生成首页、站地图的模板文件路径的自定义设置。 5、修改了模板标签的解析内核。生成静态时避免了1.0资源消耗过大的问题。 后台登陆地址:admin/login.asp 帐号:admin   密码:123456

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值