Linux系统编程全面学习

news2024/12/26 22:11:27

应用层:写一个QT可执行程序、一个C程序
驱动层:写一个LED、蜂鸣器、pwm驱动
硬件层:焊接、layout

Linux系统介于应用层和驱动层之间,Linux系统会向应用层提供接口,学习使用的基本是Linux内核向用户提供的接口或者可以说是函数

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	//argc: 命令行中参数的个数 文件名也算参数
	//argv: 命令行中的参数 
	printf("argc is %d\n",argc);
	for(int i=0;i<argc;i++)
	{
		printf("argv is %s\n",argv[i]);
	}
	return 0;
}

标准IO和IO文件

文件IO: 文件lO就是直接调用内核提供的系统调用函数。

标准IO: 标准IO就是间接调用系统调用函数,是C库函数。

在这里插入图片描述

两者区别

文件lQ是直接调用内核提供的系统调用函数,头文件是unistd.h,标准lO是间接调用系统调用函数,头文件是stdio.h,文件lO是依赖于Linux操作系统的。文件IO围绕着 文件操作符 操作的
标准IO是不依赖操作系统的,所以在任何的操作系统下,使用标准IO,也就是C库函数操作文件的方法都是相同的。标准IO围绕着 流 操作

man 2 open 查看手册

1、open函数

文件描述符

对于文件IO来说,一切都是围绕文件描述符来进行的。在Linux系统中,所有打开的文件都有一个对应的文件描述符。文件描述符的本质是一个非负整数,当我们打开一个文件时,系统会给我们分配一个文件描述符。

当我们对一个文件做读写操作的时候,我们使用open函数返回的这个文件描述符会标识该文件,并将其作为参数传递给read或者write函数。
在posix.1应用程序里面,文件描述符0,1,2分别对应着标准输入,标准输出,标准出错。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname,int flags); //失败返回-1,成功返回文件标识符, flags可以多个一起用,
int open(const char *pathname,int flags, mode_t mode); //flags用到了O_CREAT,就要用第三个参数(文件权限)

Flags
 The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR.  These request opening
 the file read-only, write-only, or read/write, respectively.

In addition, zero or more file creation flags and file status flags can be bitwise-or'd in flags.  The  file  creation
flags  are  O_CLOEXEC,  O_CREAT,  O_DIRECTORY,  O_EXCL, O_NOCTTY, O_NOFOLLOW, O_TMPFILE, and O_TRUNC.  The file status
flags are all of the remaining flags listed below.  The distinction between these two groups of flags is that the file
creation  flags affect the semantics of the open operation itself, while the file status flags affect the semantics of
subsequent I/O operations.  The file status flags can be retrieved and (in some cases) modified; see fcntl(2) for  de‐
tails.

访问权限用数字表示
可执行-1
可写-2
可读-4
可读可写就是和6,没有权限0

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	//argc: 命令行中参数的个数 文件名也算参数
	//argv:命令行中的参数 
	int fd;
	fd=open("open_a.c",O_CREAT|O_RDWR,0666);
	if(fd<0)
	{
		printf("open is error\n");
	}
	printf("fd is %d\n",fd);
	
	return 0;
}
//fd is 3
-rw-rw-r--
- : 0
rw-  : r=4 w=2 -=0 ,所以最后=6
rw- 6
r--  4
所以是0664
umask=0002
mode_t mode=0666
这里的值等于 mode_t mode & (~umask)

2、close函数

#include <unistd.h>

close(fd);

文件描述符是有限的,一般1024个,所以一般用完就关闭文件描述符

3、read函数

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
//返回 long int 读取到的字节数
//buf: 存放数据
//count: 要读取的字节数


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>

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

	int fd;
	fd=open("open_a.c",O_RDWR);

	char buf[32]={0};
	ssize_t ret;
	ret=read(fd,buf,32);
	
	if(ret<0){
	printf("read is error");
	return -2;
	}
	printf("buf is %s\n",buf);
	printf("ret is %ld\n",ret);

	ret=read(fd,buf,32);
	printf("ret is %ld\n",ret);
	
	close(fd);
	return 0;
}


//buf is helloword!
//ret is 11
//ret is 0

4、write函数

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd
int main(int argc, char* argv[])
{

	int fd;
	char buf[32]={0};
	ssize_t ret;
	fd=open("open_a.c",O_RDWR);

	write(fd,"hello\n",6);
	return 0;
}

5、综合练习

通过命令行,把 a.c 文件的内容写到b.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	//步骤一:判断命令行中的参数
	if(argc!=3){
		printf("Usage:%s <src file><obj file>\n",argv[0]);
		return -1;
	}
	//步骤一:定义变量
	int fd_src,fd_obj;
	char buf[32]={0};
	ssize_t ret;
	//步骤三:打开文件获得文件描述符
	fd_src=open(argv[1],O_RDWR);
	fd_obj=open(argv[2],O_RDWR|O_CREAT,0666);

	//步骤四:读写操作
	while((ret=read(fd_src,buf,32))!=0)
	{
		write(fd_obj,buf,ret);
	}
	close(fd_src);
	close(fd_obj);

	return 0;
}

6、lseek函数

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd:文件描述符
off_t offset:相对于当前位置的偏移
whence:当前位置 。SEEK_SET:文件开头  SEEK_CUR:文件读写指针位置 EEK_END:文件末尾

returns: the resulting offset location as measured in bytes from the  beginning  of the file:基于此还可以确定文件长度
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>

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

	int fd;
	fd=open("open_a.c",O_RDWR);
	if(fd<0)
	{
		printf("open is error\n");
		return -1;
	}

	char buf[32]={0};
	ssize_t ret;
	ret=read(fd,buf,32);
	if(ret<0){
	printf("read is error");
	return -2;
	}
	printf("buf is %s",buf);
	printf("ret is %ld\n",ret);
	
	lseek(fd,0,SEEK_SET);
	ret=read(fd,buf,32);
	printf("buf is %s",buf);
	printf("ret is %ld\n",ret);
	close(fd);
	return 0;
}

目录IO

围绕着目录进行操作

mkdir     创建目录
opendir  打开目录
readdir   读取目录
closedir  关闭目录
查询手册 man 3 函数

1、mkdir 函数

#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);

pathname: 路径名
mode: 权限
return:  zero on success, or -1 if an error 
#include <sys/stat.h>
#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	int ret;
	if(argc!=2)
	{
		printf("usage %s <name file>\n",argv[0]);
		return -1;
	}
	ret=mkdir(argv[1],0666);
	if(ret<0)
	{
		printf("mkdir is error\n");
		return -2;
	}
	printf("mkdir is ok\n");

	return 0;
}

在这里插入图片描述

2、opendir 和closedir 函数

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);

name:地址
return: 成功目录流指针,失败NULL。和文件标识符类似

#include <sys/types.h>
#include <dirent.h>

int closedir(DIR *dirp);
returns 0 on success.  On error, -1 
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	int ret;
	DIR*dp;
	if(argc!=2)
	{
		printf("usage %s <name file>\n",argv[0]);
		return -1;
	}
	dp=opendir(argv[1]);
	if(dp!=NULL)
	{
		printf("opendir is ok\n");
		
	}
	closedir(dp);

	return 0;
}

3、readdir 函数

可以读取目录下所有文件包含目录和文件

#include <dirent.h>

struct dirent *readdir(DIR *dirp);

Return:On success, readdir() returns a pointer to a dirent structure.  (This structure may be statically  allocated;  do  not attempt to free(3) it.)

 struct dirent {
     ino_t          d_ino;       /* Inode number  文件的ID号*/
     off_t          d_off;       /* Not an offset; see below */
     unsigned short d_reclen;    /* Length of this record */
     unsigned char  d_type;      /* Type of file; not supported by all filesystem types */
     char           d_name[256]; /* Null-terminated filename */
 };
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	int ret;
	DIR*dp;
	struct dirent*dir; 
	if(argc!=2)
	{
		printf("usage %s <name file>\n",argv[0]);
		return -1;
	}
	dp=opendir(argv[1]);
	if(dp!=NULL)
	{
		printf("opendir is ok\n");
		
	}
	//循环读取
	while(1)
	{
		dir=readdir(dp);
		if(dir!=NULL){
			printf("filename is %s\n",dir->d_name);
		}else{
			break;
		}
	}
	closedir(dp);
	
	return 0;
}

在这里插入图片描述

4、综合练习

通过命令行,把 a.c 文件的内容写到b.c
在此基础上,
1、打印拷贝的目录下所有文件名,拷贝需要的文件
2、通过键盘输入要拷贝的文件路径和文件名等信息
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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


	int fd_src,fd_obj;
	char buf[32]={0};
	char file_path[32]={0};
	char file_name[32]={0};
	
	ssize_t ret;
	DIR*dp;
	struct dirent *dir;
	printf("please enter the file path\n");
	scanf("%s",file_path);
	
	//打开目录,打印文件
	dp=opendir(file_path);
	if(dp==NULL){printf("open is error");return -1;}
	printf("open is ok\n");
	
	while(1){
		dir=readdir(dp);
		if(dir!=NULL){
			printf("filename is %s\n",dir->d_name);
		}else
			break;
		
	}
	
	
	//拷贝文件
	printf("please enter the source file name\n");
	scanf("%s",file_name);
		
	char *p=(strcat(strcat(file_path,"/"),file_name)); //查看输入路径有无错误
	printf("%s\n",p);
	
	
	fd_src=open(strcat(strcat(file_path,"/"),file_name),O_RDWR);
	//fd_src=open(file_name,O_RDWR);
	if(fd_src<0)
	{
		printf("open is error\n");
	}
	printf("please enter the dest file name\n");
	scanf("%s",file_name);
	fd_obj=open(file_name, O_RDWR|O_CREAT,0666);
	
	if(fd_obj<0)
	{
		printf("open fd_obj is error\n");
	}
	//步骤四:读写操作
	while((ret=read(fd_src,buf,32))!=0)
	{
		write(fd_obj,buf,ret);
	}
	closedir(dp);
	close(fd_src);
	close(fd_obj);

	return 0;
}

1、定义

  • 一种可执行的二进制文件,编译好的代码。
  • 使用库可以提高开发效率
  • Linux库分为静态库和动态库
    • 静态库:静态库在程序编译的时候会被链接到目标代码里面。所以我们的程序运行就不再需要该静态库了。因此编译出来的体积就比较大,静态库以lib开头 .a结尾(10个程序都需要某个静态库,编译的时候就需要链接10次,不太方便)
    • 动态库: 动态库(也叫共享库)在程序编译的时候不会被链接到目标代码里面,所以我们的程序运行就需要该动态库。因此编译出来的体积就比较小。静态库以lib开头 .so结尾(如果程序需要某个动态库,那移植程序的时候不太方便,因为需要环境里面有这个库)
    • 两者最大的区别就是库的加载时间不同,静态库的函数在程序编译时会被拷贝过来,而动态库不是在编译阶段是在程序运行时载入的

2、静态库的制作和使用

  1. 编写或准备库的源代码

mylib.c

#include <stdio.h>
void mylib(void);
void mylib(void)
{
	printf("this is mylib\n");
}

test.c

#include <stdio.h>
void mylib(void);
int main(void)
{
	mylib();
	return 0;
}
  1. 将源码.c文件编译生成.o文件
gcc -c mylib.c  编译成.o
  1. 使用ar命令创建静态库
ar cr libmylib.a mylib.o   

c:create  r:replace 覆盖
libmylib.a :库文件名
mylib :库名
  1. 测试库文件
gcc test.c -lmylib -L. -o test
-l库名
-L库文件路径

3、动态库的制作和使用

  1. 编写或准备库的源代码
  2. 将源码.c文件编译生成.o文件
gcc -c -fpic mylib.c  -fpic产生位置无关代码,因为动态库可以被任何位置代码调用
  1. 使用gcc命令创建动态库
gcc -shared -o libmylib.so mylib.o    -shared生成动态库

  1. 测试库文件
    在运行程序前需要配置动态库路径

如果我们的程序代码用到了库文件里面的函数,我们在编译的时候需要链接库。系统默认会在/lib或者/usr/lib去找库文件。或者在编译的时候我们制定库的路径。|

第一种方法:
将生成的动态库拷贝到lib或者/usr/lib里面去,因为系统会默认去这俩个路径下寻找。(自己写的库不建议拷贝过去)
第二种方法:
把我们的动态库所在的路径加到环境变量里面去,比如我们动态库所在的路径为/home/test,我们就可以这样添加。(临时的)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/test动态库所在路径
第三种方法:
修改ubuntu下的配置文件/etc/ld.so.conf,我们在这个配置文件里面加入动态库所在的位置,然后使用命令Idconfig更新目录。(root用户)

然后执行代码

gcc -test.c -lmylib -L. -o test
./test

进程

1、进程定义、进程ID,进程通信,进程的状态

程序是保存在存储介质上的经过编译的可执行二进制文件,是静态的。
进程指的是正在运行的程序

打开任务管理器可以看到后台有很多进程
在这里插入图片描述
那一个CPU如何处理这么多进程-不断切换
在这里插入图片描述

进程ID:每个进程都有唯一的标识符,简称PID

进程之间不能通过应用层直接交流通信,需要通过Linux内核,在里面创造一个对象(特殊的文件),然后交流。

进程间通信的方法:
管道通信:有名管道,无名管道
信号通信:信号的发送,信号的接受,信号的处理
IPC通信:共享内存,消息队列,信号灯
Socket通信:一个网络中的两个进程通信

2、进程控制

(1)fork函数

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);  //create a child process

On success, the PID of the child process is returned in the parent, and 0 is returned in the child.  
On failure, -1 is returned in the parent, no child process is created
返回值: fork函数有三种返回值,在父进程中,fork返回新创建的子进程的PID,在子进程中,fork返回0,如果出现错误,fork返回一个负值。

getpid() 获得当前进程的pid
getppid() 获得当前进程的父进程的pid
#include<stdio.h>
#include<unistd.h>
int main(void)
{
	pid_t pid;
	pid=fork();
	if(pid<0)
	{
		printf("fork is error");
		return -1;
	}
	//父进程
	if(pid>0)
	{
		printf("This is parent pid %d\n",getpid());
	}
	//子进程
	if(pid==0)
	{
		printf("this is child pid %d, parent pid %d\n",getpid(),getppid());
	}
	return 0;
}
This is parent pid 13032
This is child pid 13033, parent pid 13032

子进程是父进程的拷贝,父进程的执行是从头开始的,子进程的执行是从fork()开始的
父子进程执行的顺序不一定

(2)exec函数族

利用exex可以实现子进程完成不同的程序, 理解为换核不换壳:不执行原来复制的父进程的程序,而是执行execl里的执行文件
在Linux中并没有exec函数,而是有6个以exec开头的函数族,下面列举了exec函数族的6个函数成员的函数原型。

int execl(const char *path, const char *arg,...)  最常用的
int execv(const char *path, char *const argv[)
int execle(const char *path, const char *arg, .... char *const envp[])
int execve(const char *path, char *const argv[, char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, char *const argv[)

在Linux中使用exec函数族主要有以下两种情况:
1.当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec函数族让自己重生。
2.如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。

int execl(const char *path, const char *arg,...)
const char *path:可执行文件的完整路径
const char *arg:可执行文件名,可执行文件的参数,NULL结尾
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(void)
{
	pid_t pid;
	pid=fork();
	if(pid<0)
	{
		printf("fork is error");
		return -1;
	}
	//父进程
	if(pid>0)
	{
		printf("This is parent pid %d\n",getpid());
	}
	//子进程
	if(pid==0)
	{
		printf("this is child pid %d,parent pid %d\n",getpid(),getppid());
		execl("/home/yilp/Linux_pro/进程/hello","hello",NULL);
		exit(1);//不会执行后面的程序了
	}
	return 0;
}
利用子进程执行shell命令 ls -a

exit(x) 函数 退出进程,x=0表示进程执行成功后退出,1表示失败后退出

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(void)
{
	pid_t pid;
	pid=fork();
	if(pid<0)
	{
		printf("fork is error");
		return -1;
	}
	//父进程
	if(pid>0)
	{
		printf("This is parent pid %d\n",getpid());
	}
	//子进程
	if(pid==0)
	{
		printf("this is child pid %d,parent pid %d\n",getpid(),getppid());
		//execl("/home/yilp/Linux_pro/进程/hello","hello",NULL);
		execl("/bin/ls","ls","-a",NULL);
		exit(1);//子进程执行成功后不会执行后面的程序了
	}
	return 0;
}

//打印
//This is parent pid 13352
//this is child pid 13353,parent pid 1
//yilp@yilp-virtual-machine:~/Linux_pro/进程$ .  ..  execl  execl.c  fork  fork.c  hello.c

(3)ps 和 kill 命令

ps命令

ps命令可以列出系统中当前运行的那些进程。命令格式: ps [参数]
命令功能:用来显示当前进程的状态
常用参数:a、u 、x

a:显示进程执行的可执行文件
u:显示进程归属的用户 和 相关内存使用情况
x:显示不关联终端的进程
在这里插入图片描述

kill命令

kill命令用来杀死进程举例: kill -9(SIGKILL) PID SIGKILL这个信号由9表示
有以下kill 信号
在这里插入图片描述
sudo kill -9 pid

(4)孤儿进程和僵尸进程

孤儿进程:
父进程结束以后,子进程还未结束,这个子进程就叫做孤儿进程。孤儿进程会被系统的init进程也就是(PID号=1,也不一定是1)的进程领养,init进程也就变成了孤儿进程的父进程。
僵尸进程:
子进程结束以后,父进程还在运行,但是父进程不去释放进程控制块(子进程结束后,需要父进程去释放资源,这也是为什么孤儿进程会被领养,因为要释放资源).这个子进程就叫做僵尸进程,僵尸进程是死掉的进程,状态为Z+。

僵尸进程如果父进程挂掉了,就变成了孤儿进程,然后被领养

(5)wait 函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);  //阻塞当前进程,分析当前进程的某个子进程是不是已经退出了,如果有就会去回收释放这个子进程,如果没有找到就会一直阻塞在这里
与wait函数 有关的两个宏定义
WIFEXITED(wstatus): 子进程正常退出,宏定义为真
WEXITSTATUS(wstatus): 正常退出,宏定义的值为子进程的退出值

return:回收的子进程pid,失败-1

3、守护进程

定义:守护进程是运行在后台,不和任何控制终端关联

创建:必须作为init进程的子进程;不和终端交互

  • 1、使用fork 函数创建子进程,然后父进程使用 exit 进程 退出
  • 2、调用 setsid 函数使得子进程抛弃原来拷贝父进程的会话进程组和控制终端等的控制,新建一个(就是新的会话的首进程,新的进程组里的唯一进程,而且没有终端),不和终端交互
  • 3、调用chdir函数,将当前的工作目录改成根目录,增强程序的健壮性(不是必要的)
  • 4、重设我们umask文件掩码,增强程序的健壮性(因为子进程复制了父进程的掩码,不是必要的)
  • 5、关闭文件描述符(0 标准输出,1 标准输入,2 标准出错,守护进程不使用终端了,用不着),节省资源 (不是必要的)
  • 6、执行我们需要执行的代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(void)
{
	//1、创建子进程
	pid_t pid;
	pid=fork();
	//退出父进程
	if(pid>0)
	{
		exit(0);
	}
	if(pid==0)
	{
		//2、新建会话
		setsid();
		//3、更改工作目录
		chdir("/");
		//4、重设umask  umask() 会将调用进程的文件mode创建为掩码(umask) & mode
		umask(0);
		//5、关闭文件描述符
		close(0);
		close(1);
		close(2);
		//6、子进程需要执行的代码
		while(1){
			
		}
	}
	return 0;
}

可以看到守护进程在后台运行
在这里插入图片描述

4、进程通信

(1)管道通信

a. 无名管道
无名管道:在文件系统中不存在这样一个文件,只能实现有关系的进程间通信,要在`fork`函数创建子进程前创建,确保操作同一个管道
#include <unistd.h>
int pipe(int pipefd[2]);
int pipefd[2] 不是传入管道的文件描述符,而是返回了读写两个端
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main(void)
{
	int fd[2];
	char buf[32]={0};
	pid_t pid;

	pipe(fd);
	printf("fd[0]is %d\n",fd[0]);//3
	printf("fd[1]is %d\n",fd[1]);//4
	
	pid=fork();
	if(pid>0)
	{
		int status;
		close(fd[0]);
		write(fd[1],"hello",5);
		close(fd[1]);
		wait(&status);
		exit(0);
	}
	if(pid==0)
	{
		close(fd[1]);//子进程不写,只读,关闭写描述符
		read(fd[0],buf,32); //
		printf("buf is %s\n",buf);
		close(fd[0]);
		exit(0);
	}
	return 0;
}
//fd[0]is 3
//fd[1]is 4
//buf is hello
b. 有名管道
在文件系统中存在这样一个文件,可以让互不相干的进程进行通信。特殊的文件,不能用create创建,用mkfifo命令或者函数创建
					然后再用 open 和 read 函数操作这个文件就可以了。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode); // 权限mode(mode & ~umask).

返回值:成功0;失败-1

写数据的进程

int main(int argc,char*argv[])
{
	int fd;
	int ret;
	if(argc<2){
		printf("usage:%s<file name>",argv[0]);
		return -1;
	}	
	// access()函数 判断文件是否存在
	if(access(argv[1],F_OK)==-1){
		ret=mkfifo(argv[1],0666);
		return -2;	
	}
	fd=open(argv[1],O_WRONLY);
	while(1){
		sleep(1);
		write(fd,"hello",5);
	}
	close(fd);
	return 0;
}

读数据的进程

int main(int argc,char*argv[])
{
	int fd;
	int ret;
	char buf[32]={0};
	
	if(argc<2){
		printf("usage:%s<file name>",argv[0]);
		return -1;
	}	
	if(access(argv[1],F_OK)==-1){
		ret=mkfifo(argv[1],0666);
		return -2;	
	}
	fd=open(argv[1],O_RDONLY);

	while(1){
		sleep(1);
		read(fd,buf,32);
		printf("buf is %s\n",buf);
		memset(buf,0,sizeof(buf));//用0填充buf指针内存的前32个字节
	}
	close(fd);
	return 0;
}
//void *memset(void *s, int c, size_t n);
//The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c.
//用常数字节 c 填充 指针 s 指向的内存区域的前 n 个字节。

(2)信号通信:发送、接收、处理

系统里有各种信号,比如按键、硬件变化

a. 信号发送
kill函数:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid int sig);

raise函数:
#include <signal.h>
int raise(int sig);    raise函数等价于kill(getpid(),sig); 给自己发信号

 alarm函数:
 #include <unistd.h>
unsigned int alarm(unsigned int seconds);  类似于定时器,时间到了就关闭该进程
b. 信号的接收

进程要接收信号,那么进程就不能结束停止
有 while sleep(睡眠时间内不会结束) pause 三种方法

#include <unistd.h>
unsigned int sleep(unsigned int seconds);

#include <unistd.h>
int pause(void);  令目前进程暂停进入睡眠状态,知道被信号(signa)所打断
返回值:-1
d. 信号的处理

有系统默认(终止),忽略,捕获(收到这个信号,去执行一些动作)三种选择

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
简化:
signal(参数1,参数2);
参数1:我们要进行处理的信号,系统的信号我们可以在终端输入 kill -l 查看。
参数2:处理的方式(是系统默认、忽略、捕获)。

    1. signal(SIGINT ,SIG_IGN)
      SIG_IGN代表忽略SIGINT信号
    1. signal(SIGINT ,SIG_DFL)
      SIG_DFL代表执行系统默认操作,大多数信号的系统默认动作时终止该进程。
    1. signal(SIGINT ,myfun)
      捕捉SIGINT这个信号,然后执行myfun函数里面的代码,myfun由我们自己定义。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void myfun(int sig)
{
	if(sig==SIGINT){
		printf("get signal\n");
	}
}
int main(void)
{
	signal(SIGINT,SIG_IGN);//忽略ctrl+c
	//signal(SIGINT,SIG_DEF);//默认会终止
	signal(SIGINT,myfun);//执行myfun
	while(1){
		printf("wait signal\n");
		sleep(1);
	}
	return 1;
}

忽略:此时ctrl+c终止不了程序,kill -2 该程序pid 也终止不了,但其他信号可以

在这里插入图片描述
捕获
在这里插入图片描述

(3)共享内存通信

a. shmget创建
 #include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg); //allocates a System V shared memory segment
key_t key    :  IPC_PRIVATE 宏或者 ftok 函数的返回值         ipcs -m 查看当前有的共享内存段       ipcrm -m key 删除
size_t size  :   共享内存的大小
int shmflg   : 权限
return :  成功返回共享内存的标识符,失败返回-1
ftok 生成key值
//ftok - convert a pathname and a project identifier to a System V IPC key
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

使用 IPC_PRIVATE宏 来创建key值 ,key=0

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main(void)
{
	int shmid;
	shmid=shmget(IPC_PRIVATE,1024,0777);
	if (shmid<0){
		printf("shmget is error\n");
		return -1;
		
	}
	printf("shmid is %d\n",shmid);
	return 0;
}

在这里插入图片描述
使用 ftok 来创建key值, key!=0

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main(void)
{
	int shmid;
	key_t key=ftok("./memory.c",'m');
	
	shmid=shmget(key,1024,0777|IPC_CREAT); //使用ftok 权限要加上IPC_CREAT
	if (shmid<0){
		printf("shmget is error\n");
		return -1;
		
	}
	printf("shmid is %d\n",shmid);
	return 0;
}
b. shmat创建共享内存映射

用户操控时,利用 shmat函数将共享内存映射到进程中,这样就不用进入内核了,效率更高

#include <sys/types.h>
#include <sys/shm.h>函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
int shmid:共享内存的标识符,也就是shmget函数的返回值
const void *shmaddr:映射到的地址,一般写NULLNULL为系统自动帮完成映射
int shmflg:通常为0,表示共享内存可读可写,或者为SHM_RDONLY,表示共享内存只读

return:成功返回共享内存映射到进程中的地址,失败返回-1
c. shmdt删除地址映射

shmdt 将进程中的地址映射删除。当一个进程不需要共享内存的时候,使用这个函数将他从进程地址空间中脱离,并不会删除内核里面的共享内存对象。

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数: const void *shmaddr:共享内存映射后的地址
返回值:成功返回0,失败返回-1
d. shmctl 删除操共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid,int cmd, struct shmid_ds *buf);
参数: 
int shmid:要操作的共享内存的标识符

int cmd: IPC_STAT(获取对象属性)IPC_SET(设置对象属性)IPC_RMID(删除对象)

struct shmid_ds *buf:当cmd指定 IPC_STAT  / IPC_SET,用来保存或者设置的属性。
d. 基于共享内存实现线程间的通信
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
	int shmid;//共享内存id
	key_t key;//共享内存key值
	pid_t pid; //进程id
	
	char*s_addr,*p_addr;
	
	key=ftok("./memory2.c",'m');
	
	shmid=shmget(key,1024,0777|IPC_CREAT);
	if (shmid<0){
		printf("shmget is error\n");
		return -1;
		
	}
	printf("shmid is %d\n",shmid);
	
	pid=fork();
	
	if(pid>0){
		p_addr=shmat(shmid,NULL,0);//共享内存映射
		strncpy(p_addr,"hello",5);//向映射的地址写内容
		wait(NULL);
		exit(0);
	}
	if(pid==0){
		sleep(2);
		s_addr=shmat(shmid,NULL,0);
		printf("s_addr is %s\n",s_addr);
		exit(0);
	}
	
	
	return 0;
}

(4)消息队列通信

和共享内存通信类似

a. msgget 创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);  get a System V message queue identifier
参数: 
key_t key: 和消息队列相关的key值  IPC_PRIVATE 宏或者 ftok 函数
int msgflg: 访问权限
因为消息队列是一个链式队列,可以不断插入,因此,不用指定大小

返回值:成功返回消息队列的ID,失败-1

ipcs -q 查看当前系统里消息队列
b. msgctl 删除
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
 int msqid:消息队列的ID
int cmd:   IPC_STAT: 读取消息队列的属性,然后把它保存在buf指向的缓冲区。
	   	   IPC_SET: 设置消息队列的属性,这个值取自buf参数
		   IPC_RMID:删除消息队列
struct msqid_ds *buf:  消息队列的缓冲区

返回值:成功返回0,失败返回-1
c. msgsnd发送
int msgsnd(int msqid,const void *msgp, size_t msgsz, intmsgflg)
参数:
int msqid:  消息队列ID
const void *msgp:   指向消息类型的指针
size_t msgsz:   发送的消息(正文,不是整个结构体)的字节数。
int msgflg:  如果为0,直到发送完成函数才返回,即阻塞发送,IPC_NOWAIT:消息没有发送完成,函数也会返回,即非阻塞发送
返回值:成功返回0,失败返回-1

消息结构体			
struct msgbuf {
			long mtype;消息的类型
			char mtext[1];消息的内容
};
d. msgsrcv接收
ssize_t msgrcv(int msqid, void (msgp, size_t msgsz, longmsgtyp, int msgflg);
参数:
int msqid:消息队列ID,

void *msgp:接收消息的缓冲区

size_t msgsz:想要接收消息的字节数

long msgtyp:接收消息的标识   0就代表接收消息队列里的第一个消息

int msgflg:0表示阻塞读取,IPC_NOWAIT表示非阻塞读取返回值,成功返回接收到的消息的长度,失败返回-1
e. 基于消息队列实现进程间的通信
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/msg.h>


struct msgbuf{
	long mytype;
	char mtext[128];
};
int main(void)
{
	int msgid;//共享内存id
	key_t key;//共享内存key值
	pid_t pid; //进程id
	
	struct msgbuf msg;
	

	
	key=ftok("./memory2.c",'m');
	
	msgid=msgget(key,0666|IPC_CREAT);
	if (msgid<0){
		printf("msgget is error\n");
		return -1;
		
	}
	printf("shmid is %d\n",msgid);
	
	msg.mytype=1;
	strncpy(msg.mtext,"hello",5);
	
	
	
	pid=fork();
	
	if(pid>0){
		msgsnd(msgid,&msg,strlen(msg.mtext),0);
		sleep(3);
		exit(0);
	}
	if(pid==0){
		sleep(2);
		msgrcv(msgid,(void *)&msg,128,0,0);
		printf("msg is %s\n",msg.mtext);
		exit(0);
	}
	
	
	return 0;
}

(5)信号量

不以传输数据为目的,而是保护共享资源,本质是一个计数器,也是存在于内核空间中,因此使用信号量也是需要创建的

a. semget创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
:int semget(key_t key, int nsems, int semflg);函数功能:获得信号量的ID*

参数: 
key_t key:	信号量的键值。
int nsems:	信号量的数量。信号量集合中包含的信号量个数
int semflg:	标识

返回值:成功返回信号量的ID;失败返回-1
b. semctl 删除、属性设置
int semctl(int semid, int semnum, int cmd, union semun arg);

参数: 
int semid:	信号量ID,
int semnum:	信号量编号
cmd:	IPC_STAT(获取信号量的属性)IPC_SET(设置信号量的属性)IPC_RMID((删除信号量)SETVAL(设置信号量的值)

// 设置信号量属性、值 时需要用到的结构体
arg:union semun {
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo *_buf;
	};
c. semop 信号量PN操作
int semop(int semid, struct sembuf *sops, size_t nsops);

参数: 
int semid	信号量ID,
struct sembuf *sops	信号量结构体数组
size_t nsops	要操作信号量的数量

struct sembuf{
	unsigned short sem_num;  	// 要操作的信号量的编号
	short  sem_op;		//PN操作,1为V操作,释放资源。-1为P操作,分配资源。0为等待,直到信号量的值变成Oshort
	sem_flg;		//O表示阻塞,IPC_NOWAIT表示非阻塞
	};
d. 基于信号量实现子进程先运行
#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <unistd.h>

union semun {
	int val;

};
//注意不要定义struct sembuf,因为已经在#include <sys/sem.h>里定义了,会报重定义的错误
//struct sembuf{
//	unsigned short sem_num;  	// 要操作的信号量的编号
//	short  sem_op;		//PN操作,1为V操作,释放资源。-1为P操作,分配资源。0为等待,直到信号量的值变成Oshort
//	short sem_flg;		//O表示阻塞,IPC_NOWAIT表示非阻塞
//};

int main(void)
{
	int semid;
	int key;
	pid_t pid;
	union semun semun_union;
	struct sembuf sem;
	
	key=ftok("./a.c",0666);
	
	semid =semget(key,1,0666|IPC_CREAT);
	
	semun_union.val=0;

	semctl(semid,0,SETVAL,semun_union);
	
	pid=fork();
	if(pid>0){
		sem.sem_num=0;
		sem.sem_op=-1;
		sem.sem_flg=0;

		semop(semid,&sem,1);
		printf("this is parent\n");
		
		sem.sem_num=0;
		sem.sem_op=1;
		sem.sem_flg=0;

		semop(semid,&sem,1);
	}
	if(pid==0){
		sleep(2);
		printf("this is son\n");
		
		sem.sem_num=0;
		sem.sem_op=1;
		sem.sem_flg=0;

		semop(semid,&sem,1);
	}
	
	return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2064096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

理解Tomcat的IP绑定与访问控制

在使用Spring Boot开发应用时&#xff0c;内置的Tomcat容器提供了灵活的网络配置选项。特别是&#xff0c;当计算机上有多个网卡时&#xff0c;如何配置server.address属性显得尤为重要。本文将详细探讨不同IP配置对Tomcat服务访问的影响。 多网卡环境下的IP配置 假设你的计算…

java 8种基础数据类型

1、数据范围 2、各个类型转换 实线转换&#xff1a;无信息丢失的自动转换&#xff0c;反方向需要强制类型转换&#xff0c;如&#xff08;int) 虚线转换&#xff1a;可能存在精度丢失 精度丢失示例如下&#xff1a; long l 123456787654321L; float f l; System.out.prin…

框架漏洞大全【万字总结】

文章目录 常见语言开发框架&#xff1a;Thinkphp远程代码执行5.0.23 rce介绍影响版本复现 CNVD-2018-24942介绍影响版本复现 任意文件包含包含日志-3.2x介绍影响版本复现 包含语言&#xff08;QVD-2022-46174&#xff09;介绍影响版本复现 sql注入漏洞(5.0.x)介绍影响版本复现 …

太上老君的“三味真火”也可以提升3D NAND可靠性!

《西游记》中孙悟空因在太上老君的炼丹炉中历经九九八十一难&#xff0c;最终炼就了一双能够洞察一切妖魔鬼怪真身的“火眼金睛”。这双神奇的眼睛&#xff0c;仿佛预示着一种古老的智慧——通过火的考验&#xff0c;可以淬炼出更加坚韧的灵魂。 而在现代科技的洪流中&#xff…

软件测试-自动化测试

自动化测试 测试人员编写自动化测试脚本&#xff0c;维护并解决自动化脚本问题 自动化的主要目的就是用来进行回归测试 回归测试 常见面试题 ⾃动化测试能够取代人工测试吗&#xff1f; ⾃动化测试不⼀定⽐人工测试更能保障系统的可靠性&#xff0c;⾃动化测试是测试⼈员手…

vue2项目从0到1记录

脚手架需要安装完 npm install -g vue/cli1. 使用脚手架创建项目 vue create 项目名2. 引入样式重置normalize.css插件 // 统一浏览器样式 安装&#xff1a;npm install --save normalize.css 引入&#xff1a;import normalize.css/normalize.css3. 根据部署环境判断是否要…

MyBatis[进阶]

大纲: 动态SQL查询 留言板 1. 动态SQL 1.1 <if> 我们都注册过一些信息,有的信息是非必填项,改如何实现呢? 这个时候就需要使⽤动态标签来判断了 ⽐如添加的时候性别gender为⾮必填字段&#xff0c;具体实现如 下&#xff1a; 注解: 如果性别为空: 如果性别不为空:…

你真的会用大模型吗,探索提示词工程的魅力

相信在这一两年内&#xff0c;每个人都尝试使用了各种大模型。不知大家有没有发现&#xff0c;它们的质量参差不齐&#xff0c;回答的内容也不一定准确。随着人工智能技术的快速发展&#xff0c;越来越多的模型被开发出来并用于各种应用&#xff0c;但并非所有模型都能够提供可…

Leetcode每日刷题之904.水果成篮

1.题目解析 本题的题目要求较长&#xff0c;不过理解起来较为简单&#xff0c;就是在给定数组内找出最长子数组&#xff0c;并且该最长子数组只能有两种数字&#xff0c;最后返回该符合条件的最长子数组的长度即可 题目来源&#xff1a;904.水果成篮 2.算法原理 本题的核心是找…

组件提前渲染

问题&#xff1a; 组件正常引入并使用的过程中&#xff0c;出现组件第一次渲染不显示&#xff0c;只有再次刷新页面才显示的问题 <el-table-column label"图纸规定" align"center" prop"tzgd" v-if"mbform.zbzd.tzgd" width"…

动手实现基于Reactor模型的高并发Web服务器(一):epoll+多线程版本

系统流程概览 main函数 对于一个服务器程序来说&#xff0c;因为要为外部的客户端程序提供网络服务&#xff0c;也就是进行数据的读写&#xff0c;这就必然需要一个 socket 文件描述符&#xff0c;只有拥有了文件描述符 C/S 两端才能通过 socket 套接字进行网络通信&#xff0…

【深海王国】小学生都能玩的单片机!番外2:Arduino控制其他元器件

Hi٩(๑ ^ o ^ ๑)۶, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~辛勤工作的你今天也辛苦啦 (o゜▽゜)o☆ 今天大都督为大家带来单片机的新番外系列——小学生都能玩的单片机&#xff01;番外2&#xff1a;Arduino控制其他元器件&#xff0c;带你学习如何使用Ard…

性能测试面试问答题

1、性能测试怎么测试&#xff1f; 性能测试其实就是通过自动化工具模拟多种正常、峰值以及异常负载来对系统的各项性能指标进行测试。负载测试和压力测试都属于性能测试&#xff0c;二者可结合使用。 性能指标主要有平均响应时间、90%响应时间、吞吐量、吞吐率&#xff0c;每…

基于SpringBoot的私房菜定制上门服务系统的设计与实现pf

TOC springboot512基于SpringBoot的私房菜定制上门服务系统的设计与实现pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域…

图书项目要点

一、搭建项目 使用tarojs/cli进行搭建 taro init [项目名] 二、具体页面 页面声明&#xff1a; 在【app.config.ts】中对主页面进行声明&#xff1a;组件页面可以不用声明 pages: ["pages/index/index",pages/user/index,pages/book/index,], tabbar制作&…

Linux系统编程(14)UDP全双工通信和TCP半双工通信

一、UDP全双工通信 UDP通信基础&#xff1a; recvfrom函数 recvfrom 是一个用于接收数据的函数&#xff0c;&#xff0c;但 recvfrom 不仅接收数据&#xff0c;还可以获取发送数据的地址信息。 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sock…

【vue3|第25期】Vue3中的useRoute:轻松访问路由信息

日期&#xff1a;2024年8月21日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉在这里插入代码片得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不…

撰写文献综述策略

撰写文献综述 文献综述在形式上与任何其他类型的学术文本没有任何不同&#xff0c;因为它也具有基本部分。每个部分中包含的内容取决于您撰写文献综述的目的&#xff1a; 简介 此部分应明确定义评论的目的和重点。论文&#xff1a;如果您将评论作为毕业论文或学位论文的一部分…

文件IO函数练习

作业&#xff1a;使用write和read完成文件的拷贝。 代码 #include <myhead.h>int main(int argc, const char *argv[]) {int fd open("./1.txt",O_RDONLY);//已只读打开被拷贝文件if(-1 fd){perror("open");return -1;}int fd1 open("./2.…

Python使用QtSide6(PyQt)编写界面

1、安装QtSide6 开始菜单cmd 创建虚拟环境 python -m venv env2 进入虚拟环境 call env2/scripts/activate 安装Pyside6 pip install Pyside6 2、设计Qt界面 打开designer.exe&#xff0c;设计界面 点击菜单【窗体】【View Python Code...】&#xff0c;点击【全部复制】…