image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
示例 编写mysignal.c,然后编译运行
1 #include <signal.h> // 处理信号的头文件
2 #include <string.h>
3 #include <sys/types.h>
4 #include <unistd.h>
5 #include <iostream>
6
7 //信号处理时的变量只能用这个支持原子操作的
8 sig_atomic_t sigusr1_count = 0;
9 //因为linux中信号处理相关的函数时C语言写的,
10 //因此也要用C语言来写我们自定义的信号处理函数
11 extern "C" {
12
13 void OnSigUsr1(int signal_number)
14 { ++sigusr1_count; }
15
16 }
17
18 int main ()
19 {
20 std::cout << "pid: " << (int)getpid() << std::endl;
21 //信号结构体
22 struct sigaction sa;
23 memset( &sa, 0, sizeof(sa) );
24 //将自定义的信号处理函数的指针赋值给结构体成员
25 sa.sa_handler = &OnSigUsr1;
26 //SIGUSR1 linux提供的可供用户自定义的信号,值为10信号编号。
27 //设置信号配置函数
28 sigaction( SIGUSR1, &sa, NULL );
29 std::cout << "SIGUSR1 value: " << SIGUSR1 << std::endl; //将打印出10
30 std::cout << "SIGUSR1 counts: " << sigusr1_count << std::endl;
31 std::cout << "sleep 100 seconds ..." << std::endl;
32 sleep( 100 );
33 // 在终端中输入kill –s SIGUSR1 pid或者kill -10 pid,
34 // 将使得本程序信号函数执行,信号计数器将递增。sleep立即终止,然后往下执行
35 gtd::cout << std::endl<<"SIGUSR1 counts: " << sigusr1_count << std::endl;
36 if(sigusr1_count == 1)
37 std::cout<<"reseive SIGUSR1 success !"<<std::endl;
38 return 0;
39 }
image.png
在sleep100秒时,当前shell会被占用,可打开另一个shell,然后执行下图的任意一条命令来发送信号。
image.png
-s SIGUSR1 和 -10 是等价的。SIGUSR1是由宏定义的#define SIGUSR1 10
即信号值为10。
同理,在强制杀死一个进程时在命令行执行的#kill -9 pid等价于#kill -s SIGKILL pid
进程管理
image.pngimage.png
fork()实例1
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main ()
{
cout << "the main program process ID is " << (int)getpid() << endl;
pid_t child_pid = fork();
//注意:fork之前的代码只有父进程执行,
//fork之后的代码父子进程都有机会执行, 受代码逻辑的控制而进入不同分支。
if( child_pid != 0 )
{
//只有父进程才能进的分支
sleep(3); //让父进程停3秒,看看子进程是否会先执行它的分支
cout << "this is the parent process, with id " << (int)getpid() << endl;
cout << "the child’s process ID is " << (int)child_pid << endl;
}
else
{
//child_pid ==0,只有子进程才能进的分支
cout << "this is the child process, with id " << (int)getpid() <<endl;
}
cout<<"父子进程都执行的代码。"<<endl;
return 0;
}
编译执行结果
image.png
可见:fork之前的代码只有父进程执行,fork之后的代码父子进程都有机会执行, 受代码逻辑的控制而进入不同分支。父子进程分别异步执行,不再互相影响。
fork()实例2
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int global = 100;
int main (void) {
int local = 200;
char* heap = (char*)malloc (256 * sizeof (char));
sprintf (heap, "ABC");
printf ("父进程:%d %d %s\n", global, local, heap);
pid_t pid = fork ();
if (pid == -1) {
perror ("fork");
return -1;
}
if (pid == 0) {
global++;
local++;
sprintf (heap, "XYZ");
printf ("子进程:%d %d %s\n", global, local, heap);
sleep(3);
}
printf ("父子进程都执行的代码:%d %d %s\n", global, local, heap);
free (heap);
return 0;
}
代码编译执行结果:
image.png
可见:子进程是父进程的副本,子进程获得父进程数据段和堆栈段(包括I/O流缓冲区)的拷贝,不是共享数据,只是子进程共享父进程的代码段。
image.png更多fork相关例子,可以看这篇博文:https://blog.csdn.net/meetings/article/details/47123359
image.png
image.png
image.png
子进程成为僵尸进程的例子
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main ()
{
pid_t child_pid;
child_pid = fork();
if( child_pid > 0 ) // 父进程,速度睡眠六十秒
sleep( 60 );
else // 子进程,立即退出,没有让父进程获取到退出状态并清除
exit( 0 );
return 0;
}
子进程的异步清除
- SIGCHLD信号:子进程终止时,向父进程自动发送,编写此信号处理例程,异步清除子进程。
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
sig_atomic_t child_exit_status;
extern "C" {
void CleanUp( int sig_num )
{
int status;
wait( &status ); // 清除子进程
child_exit_status = status; // 存储子进程的状态
}
}
int main ()
{
// 处理SIGCHLD信号
struct sigaction sa;
memset( &sa, 0, sizeof(sa) );
sa.sa_handler = &CleanUp;
sigaction( SIGCHLD, &sa, NULL );
// 正常处理代码在此,例如调用fork()创建子进程
return 0;
}
image.png
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/fs.h>
int main()
{
pid_t pid = fork();
if( pid == -1 )
return -1;
else if( pid != 0 )
exit( EXIT_SUCCESS ); //父进程退出
// 接下来就都是子进程运行
if( setsid() == -1 )
return -2;
// 设置工作目录
if( chdir( "/" ) == -1 )
return -3;
// 重设文件权限掩码
umask( 0 );
// 关闭文件描述符,因为只打开了默认的0,1,2三个输入流、输出流、错误流三个
for( int i = 0; i < 3; i++ ) close( i );
// 重定向标准流,将0,1,2都挂载到哑设备上,相当于什么都不干,不输入也不输出
open( "/dev/null", O_RDWR ); // stdin
dup( 0 ); // stdout
dup( 0 ); // stderr
// 守护进程的实际工作代码在此
return 0;
}
进程间通信
进程间通信image.png要引用的头文件在linux目录/usr/include或者/usr/include/sys下:
1、如要使用管道:#include <fcntl.h> 文件控制,<unistd.h> 一些默认的文件描述符0/1/2或符号常量等,如: image.png
2、如要使用信号量:#include <sys/sem.h> semaphore信号量。
3、如要使用共享内存:#include<sys/shm.h> share memory共享内存。
4、如要使用映射内存:#include<sys/mman.h> memory mapping内存映射。
5、如要使用消息队列:#include<sys/msg.h> message queue消息队列。
6、如要使用socket套接字:#include<sys/socket.h>
共用的几个:<sys/ipc.h> 进程间通信,<fcntl.h> 文件控制,<sys/types.h> 基本系统数据类型,<unistd.h> 多个宏定义的符号常量。
image.png分页系统的核心在于:将虚拟内存空间和物理内存空间皆划分为大小相同的页面,如4KB、8KB或16KB等,并以页面作为内存空间的最小分配单位,一个程序的一个页面可以存放在任意一个物理页面里。
分页系统的核心是页面的翻译,即从虚拟页面到物理页面的映射(Mapping)。该翻译过程如下伪代码所示:
if(虚拟页面非法、不在内存中或被保护)
{
陷入到操作系统错误服务程序
} else {
将虚拟页面号转换为物理页面号
根据物理页面号产生最终物理地址
}
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
const int buf_size = 4096;
// 向stream中写入count次msg
void Write( const char * msg, int count, FILE * stream )
{
for( ; count > 0; --count )
{
fprintf( stream, "%s\n", msg );
fflush( stream );
sleep (1);
}
}
// 从stream中读取数据
void Read( FILE * stream )
{
char buf[buf_size];
// 一直读取到流的尾部
while( !feof(stream) && !ferror(stream) && fgets(buf, sizeof(buf), stream) != NULL )
{
fprintf( stdout, "Data received: \n" );
fputs( buf, stdout );
}
}
int main()
{
int fds[2];
pipe( fds ); // 创建管道
pid_t pid = fork(); // 创建子进程
if( pid == 0 ) { // 子进程
close( fds[1] ); // 只读取,关闭管道写入端
// 将文件描述符转换为FILE *,以方便C/C++标准库函数处理
FILE * stream = fdopen( fds[0], "r" );
Read( stream ); // 从流中读取数据
close( fds[0] ); // 关闭管道读取端
}
else if( pid > 0 ) { // 父进程
char buf[buf_size]; // 数据缓冲区,末尾封装两个‘\0’
for( int i = 0; i < buf_size-2; i++ ) buf[i] = 'A' + i % 26;
buf[buf_size-1] = buf[buf_size-2] = '\0';
close( fds[0] ); // 只写入,关闭管道读取端
FILE * stream = fdopen( fds[1], "w" );
Write( buf, 3, stream );
close( fds[1] ); // 关闭管道写入端
}
return 0;
}
image.png
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
const int buf_size = 4096;
int main ()
{
int fds[2];
pipe( fds ); // 创建管道
pid_t pid = fork();
if( pid == (pid_t)0 ) // 子进程
{
close( fds[0] ); // 关闭管道读取端
dup2( fds[1], STDOUT_FILENO ); // 管道挂接到标准输出流
char * args[] = { "ls", "-l", "/", NULL }; // 使用“ls”命令替换子进程
execvp( args[0], args );
}
else // 父进程
{
close( fds[1] ); // 关闭管道写入端
char buf[buf_size];
FILE * stream = fdopen( fds[0], "r" ); // 以读模式打开管道读取端,返回文件指针
fprintf( stdout, "Data received: \n" );
// 在流未结束,未发生读取错误,且能从流中正常读取字符串时,输出读取到的字符串
while( !feof(stream) && !ferror(stream) && fgets(buf, sizeof(buf), stream) != NULL )
{
fputs( buf, stdout );
}
close( fds[0] ); // 关闭管道读取端
waitpid( pid, NULL, 0 ); // 等待子进程结束
}
return 0;
}
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
共享内存创建和连接示例
#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main ()
{
struct shmid_ds shmbuf;
int seg_size;
const int shared_size = 4096; //一个内存页面的大小
// 分配共享内存段
int seg_id = shmget( IPC_PRIVATE, shared_size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR );
// 连接共享内存段
char * shared_mem = ( char * )shmat( seg_id, 0, 0 );
printf( "Shared memory attached at %p\n", shared_mem );
// 获取段尺寸信息
shmctl( seg_id, IPC_STAT, &shmbuf );
seg_size = shmbuf.shm_segsz;
printf( "Segment size: %d\n", seg_size );
// 向共享内存区段写入字符串
sprintf( shared_mem, "Hello, world." );
// 拆卸共享内存区段
shmdt( shared_mem );
// 在不同的地址处重新连接共享内存区段
shared_mem = ( char * )shmat( seg_id, ( void * )0x5000000, 0 );
printf( "Shared memory reattached at %p\n", shared_mem );
// 获取共享内存区段中的信息并打印
printf( "%s\n", shared_mem );
// 拆卸共享内存区段
shmdt( shared_mem );
// 释放共享内存区段,与semctl类似
shmctl( seg_id, IPC_RMID, 0 );
return 0;
}
image.png代码编译执行结果:
image.png
image.png
image.png
映射内存实例:
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <wait.h>
#include <iostream>
#include <iomanip> //设定输出格式的
const int mapped_size = 4096;
const int mapped_count = mapped_size / sizeof(int);
int main( int argc, char * const argv[] )
{
// 打开文件作为内存映射的对象,确保文件尺寸足够存储1024个整数
int fd = open( argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR );
//将文件的指针(光标)定位到指定位置
lseek( fd, mapped_size - 1, SEEK_SET );
write( fd, "", 1 );
lseek( fd, 0, SEEK_SET );
//创建映射内存
int * base = ( int * )mmap( 0, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, fd, 0 );
close( fd ); // 创建映射内存后,关闭文件的文件描述符
pid_t pid = fork();
if( pid == (pid_t)0 ) // 子进程写入数据
{
// 写入数据0~1023
for( int i = 0, * p = base; i < mapped_count; *p++ = i++ )
;
//释放映射内存
munmap( base, mapped_size );
}
else if( pid > (pid_t)0 ) // 父进程读取数据
{
sleep( 10 ); // 等待10秒
for( int i = 0, *p = base; i < mapped_count; i++, p++ )
std::cout << std::setw(5) << *p << " "; //设定输出宽度w为5个字符位置
std::cout << std::endl;
munmap( base, mapped_size );
}
return 0;
}
image.png使用共享内存和映射内存比管道的优势在于,管道一次最多只能传递一个内存页面大小的数据,而共享内存和映射内存的大小不限制而且还可以跟文件挂钩,比管道更灵活。
image.png
image.png用消息队列多用于传递短消息,不用于传递太长的复杂消息。
image.png
image.png
image.png
image.png
网友评论