Linux知识点总结(文件,进程,进程间通信)

一、文件操作
windows:DOS命令
linux:shell命令
都是为了操作文件
rm -rf +目录名:删除目录
mkdir :制作目录
touch :制作文件
vi se+tab键:快速进入某个文件
命令行直接G到达文件末尾
yy p复制粘贴
命令行模式下:gg=G:自动对齐

文件操作:
打开文件、创建文件、读、写、光标移动,给文件写入结构体。
touch file  创建文件
1、open:
	(1)  int open(const char *pathname, int flags);//文件存在时用这种方式打开
		fd = creat("/home/yx/file3",S_IRWXU);//创建一个可读可写可执行的文件
	(2)  int open(const char * pathname, int flags, mode_t mode);//不存在时用这种方式先创建再打开
		 open(fd,O_RDWR|O_CREAT|O_TRUNC,0600);//文件不存在就创建存在就清空。
		open("./file",O_RDWR|O_APPEND);//每次打开都移动到文件末端换行,从文件下一行写入

		O_RDWR//可读可写
		O_CREAT//没有就创建
		O_APPEND//从文件下一行起
		!!!O_TRUNC//清空文件内容
		结论:如果没有O_TRUNC|O_APPEND,就会从文件头开始写入,会覆盖原先的文件内容
		
2、write/read:一定要关心写到哪,从哪读
	ssize_t write(int fd, const void *buf, size_t count)返回写入的字节数
	不知道文件大小时,就lseek求文件大小
	read(int fd, void *buf, size_t count);
	指针使用前一定要分配地址
	linux下指针默认分配8字节,8个字母
	注意:写完文件后,要读取的话,光标一定要移动到文件头
3、lseek:
	off_t  lseek(int fd, off_t offset, int whence);
	fd:对fd所指向的文件进行光标偏移
	offset:往后偏移这么多字节
	whence:从哪里偏移;
	SEEK_SET :光标移到文件头开始偏移
	SEEK_CUR:从光标当前位置开始偏移
	SEEK_END:从文件末尾开始偏移
	lseek返回值是从文件头到偏移到当前位置(whence)所偏移的值(反应的是文件偏移量)。
	!!!int size = lseek(fdSrc,0,SEEK_END);//求文件大小(当文件头移动到文件末有多少字节)
	lseek(fdSrc,0,SEEK_SET);
	lseek(fdSrc,-20,SEEK_CUR);//从当前位置向前偏移20字节
close:
	close(fd)关闭文件
从键盘输入输出:
0:标准输入,从键盘获取
1:标准输出,给到终端
2:标准错误
read(0,readbuf,5);//从键盘读取5个字节
write(1,readbuf,strlen(readbuf));//输出到终端上
非同步修改文件配置:
思想,把源文件的内容写到readbuf里面(计算完源文件大小后要重新把光标移到文件头),
在readbuf里面寻找需要需要修改的文件内容(一定要写入字符型数字,文件里存的都是字符),
进行修改(利用了指针偏移),此时readbuf里面的内容被修改,把readbuf里面的内容
写回到源文件可以覆盖也可以重新打开清空文件内容。

核心:char *strstr(const char *haystack, const char *needle);
在haystack字符串里面查找needle字符串。如果找到则返回到要找的字符串的开始位置处;查找失败返回空指针。
给文件写入结构体:(是打开文件)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct data
{
        int a;
        char b;
};
//给一个指定的文件写入自己想要的数据,然后读出来
//流程:数组写给源文件,源文件读到test2,对结构体&等于地址
int main()
{
        int fdSrc;
        struct data test[2] = {{98,'w'},{89,'m'}};
        struct data test1[2];
        fdSrc = open("./file",O_RDWR);
        write(fdSrc,&test,sizeof(struct data)*2);//给源文件写入后面这么多字节,写的内容是test2里面的内容
        lseek(fdSrc,0,SEEK_SET);//写完数据光标重新移到文件头要读数据
        read(fdSrc,&test1,sizeof(struct data)*2);

        printf("%d  %c\n",test1[0].a,test[0].b);
        printf("%d  %c\n",test1[1].a,test[1].b);
        close(fdSrc);
        return 0;
}

linux指针分配的是8字节,1字母1字节
-rwxrwxr-x:
-代表普通文件
rwx代表当前用户的权限4+2+1
rwx:代表同组其他用户权限4+2+1
r-x:代表其他用户可读可执行不可写
r:只读打开文本
rb:只读打开二进制文件
w:只写打开文本
wb:只写打开二进制文件
r+:可读可写打开文本
w+:可读可写创建文本

二、C标准库的文件操作

FILE *fopen(const char *path, const char *mode);//返回的是文件标识符

size_t fwrite(const void *ptr, size_t size, size_t nmemb,  FILE *stream);
参数一:要往文件写入的内容,是字符串格式
参数二:一次写入的字节数
参数三:写多少次
参数四:目标文件标识符
size_t fread(const void *ptr, size_t size, size_t nmemb,  FILE *stream);

fclose(*FILE)//关闭文件

fputc(str,fp),往目标文件写入一个字符

feof(fp)//没到达文件末尾,返回值是0,到达文件末尾,返回值不为0
c = fgetc(fp);//每次从文件里面读取一个字符存到c,读完以后自动后移
这俩经常搭配在一起使用
文件应用:
往文件写入东西时:要创建文件,而非readbuf(例如把结构体数组写到文件,把文件写到另一个结构体)
复制文件时:创建readbuf并且读写(把文件读到readbuf,把readbuf写到另一个文件)
两个文件之间的操作用readbuf(自己写cp命令)
单文件内的赋值用另外一个文件赋值作中介(把结构体写入文件)

三、进程的概念:

进程的操作:fork创建进程、vfork()创建进程,等待函数,子进程调用exit(0)退出
wait(&status);//子进程退出状态放到status里面
WEXITSTATUS(status)把子进程的退出状态解析到status里面如之前的1
getpid();//获取当前进程进程号
getppid();//获取父进程的进程号
exec族函数:
子进程使用exec调用另外一个程序来运行,该进程会被完全替换为该程序,进程号不变
调用失败返回-1,调用成功无返回值
第一个参数是:路径下哪个程序
execl("./echoarg","echoarg","abc",NULL) == -1//当前目录下运行echoarg,后面是参数,参数必须以NULL结尾
execl("/bin/ls","ls",NULL,NULL) //绝对路径下ls,传参
execl("/bin/date","date",NULL,NULL) //调用系统时间
whereis ls  查看ls的绝对路径
进程就是跑起来的程序
ps -aux|grep init  把很多进程中带init的过滤出来
top:类似windows下的任务管理器,用来查看进程的cpu占用率
每个进程都有一个非负整数表示进程号pid,类似身份证
getpid();//获取当前进程进程号
getppid();//获取父进程的进程号

pid_t fork(void);
fork函数调用成功返回两次
fork = 0,表示当前进程是子进程
fork返回值是非负数,表示当前进程是父进程,返回值为子进程pid号
创建进程就是把fork后面的代码(包括fork行)重新运行一次,不过得父进程运行完成之后再来运行子进程
这就是返回两次的概念
此时就有了父子进程的概念,程序的进程先运行,运行结束子进程运行

程序空间分配:
全局数据区∶存放全局变量,静态变量口(会被默认初始化为0)
数据段:初始化过的变量
BSS段:函数外未初始化过的变量
栈空间∶存放函数参数,局部变量,函数调用信息也保存此(栈用于保存短暂时间变量)
堆空间∶用于动态创建变量(malloc)
寄存器:最快的存储区
父进程创建子进程发生了什么呢?
1、以前的linux拷贝处理
fork()以后,同时有两个代码段,子进程会复制父进程的所有代码,形成自己独立的物理空间(A和B等价)
所以在子进程中改变的变量值不会影响父进程中变量值
2、当前的linux写时拷贝
即子进程不对父进程变量动手脚时,这些代码都是共享而非独立,当子进程想要改变父进程变量时,
就会复制一份变量用来改变,省去大篇幅的复制代码,节省空间
fork创建一个子进程的一般目的:
应用1.一个父进程希望复制自己,使父子进程同时执行不同的代码段(多个子进程同时运行),在这个网络服务进程中是常见的。父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
应用2.一个进程要执行一个不同的程序,这对shell常见的情况。在这种情况下,子进程从fork返回后立即调用exec
int main()
{
        int data;
        pid_t pid;
        while(1)
        {
                printf("please input a data:");
                scanf("%d",&data);
                if(data==1)
                {
                        pid = fork();
                        if(pid>0)
                        {

                        }
                        if(pid==0)
                        {
                                while(1)
                                {
                                        printf("this is child fork,pid is %d,my father pid id %d\n",getpid(),getppid());
                                        sleep(3);
                                }
                        }

                }
                else
                {
                        printf("do nothing \n");
                }

        }
        return 0;
}
这里,在while(1)内不断地查询输入,如果不是1就什么都不干;
如果是1就创建子进程且复制物理空间,此时父进程什么也不干,
假如有了子进程1,那么子进程1永远在循环无法退出;此时的父进程那份空间由于没有死循环
一直在查询用户输入,当用户再次输入1时,再创建子进程2,同样困死无法结束。
这俩各自都拥有自己的物理空间,各自进行自己的死循环,而父进程一直在查询用户输入,用于创建子进程
这些子进程是在争夺资源

在这里插入图片描述
vfork与fork区别:

fork创建的子进程会争夺CPU资源,不一定谁先谁后
vfork:
(1)保证子进程先运行,子进程调用exit(0)退出后父进程运行
		子进程退出一定要调用退出函数
		exit(0)推荐   
		_Exit(0)  _exit(0)
(2)使用父进程的存储空间(无自己的存储空间),不拷贝
父进程等待子进程退出:
fork() + wait() = vfork();
wait()等待子进程退出再运行父进程·并且回收子进程,避免子进程变为僵尸进程
waitpid(pid_t pid, int *status, int options);//不等待子进程,子进程会变为僵尸进程
第一个参数:进程的pid号,在父进程调用,这个pid即为子进程pid号。
第二个参数:子进程退出状态存到该地址里
第三个参数:多用WNOHANG


int main()
{
        int data = 0;
        pid_t pid;
                        pid = fork();
                        if(pid>0)
                        {
                                wait(NULL);//只等待子进程退出并且回收,不关心退出状态
                                
                                //wait(&status);//子进程退出状态放到status里面
                                //WEXITSTATUS(status)把子进程的退出状态解析到status里面如之前的1
                                
                                while(1)
                                {

                                        printf("this is fork,pid is %d\n",getpid());
                                }
                        }
                        if(pid==0)
                        {
                                while(1)
                                {
                                        data++;
                                        printf("this is child fork,pid is %d,my father pid id %d\n",getpid(),getppid());
                                        sleep(3);
                                        if(data==4)
                                        {
                                                exit(0);
                                        }
                                }
                        }
        return 0;
}

子进程与父进程同时运行,过一会父进程退出,此时子进程变为孤儿进程,系统默认把init进程作为子进程的父进程(init进程默认pid为1)

exec族函数:
子进程使用exec调用另外一个程序来运行,该进程会被完全替换为该程序,进程号不变
调用失败返回-1,调用成功无返回值
第一个参数是:路径下哪个程序
execl("./echoarg","echoarg","abc",NULL) == -1//当前目录下运行echoarg,后面是参数,参数必须以NULL结尾
execl("/bin/ls","ls",NULL,NULL) //绝对路径下ls,传参
execl("/bin/date","date",NULL,NULL) //调用系统时间
whereis ls  查看ls的绝对路径

配置环境变量的作用:系统执行程序时,都是先去环境变量目录下去寻找
可以通过把当前路径配置到环境变量路径下,不要./相对路径了
export PATH = $PATH:(pwd所指的绝对路径)

execlp("date","date",NULL,NULL)
多了个p相比于execl,p:PATH就是环境变量,即位于环境变量目录下的命令,不需要带绝对路径

char  *argv[] = {"date",NULL,NULL};
execvp("date",argv);//把参数作为argv,参数做成数组

char  *argv[] = {"date",NULL,NULL};
execv("/bin/date",argv);//把参数作为argv,参数做成数组
system:本质和execl一致,都是去执行一个shell脚本
system(“./change  file”);//在当前路径下(即命令行)执行这样的shell脚本,调用change  file修改文件配置

syetem与execl的区别:
用system还会继续回到程序中执行接下来的代码,
使用execl()程序不会回到代码中执行以下代码


popen和system相比可以获取程序的运行输出结果
ps r  筛选出正在运行的进程。
#include <stdio.h>
int main()
{
        FILE *fp;
        char text[1024]={0};
        fp = popen("ps","r");//结果保存到fp的文件流中
        int nread = fread(text,1,1024,fp);//对文件里读取要用fread
        printf("read %d byte ,context is %s\n",nread,text);
        return 0;
}

三、进程间通信
在这里插入图片描述

无名管道:创建的在内核
有名管道:创建的空间可以以文件打开操作
消息队列:队列号、进程号
共享内存:
信号量:P、V操作
我们希望进程一、二都有自己独立的空间,而在两进程中间建立某种联系空间,形成进程间通信
进程间通信又分为单机版进程间通信与网络进程通信(两个不同主机的通信)
其中单机进程通信有以下五种:
1、无名管道(半双工管道):
	同一时间数据只能在一个方向上流动,管道里数据读走就没了
	创建的管道位于内核,不计入文件
	
	pipe是创建一个无名管道,当一个管道建立时,它会创建两个参数,fd[0]为读而打开,fd[1]为写而打开;要关闭管道,只需要关闭这两个文件描述符即可。
	
	要考虑的问题:从哪写给fd[1],把fd[0]读给谁?
	
	int pipe(int pipefd[2]);//管道创建失败返回-1
	应用场景:创建管道并且创建父子进程,父进程(pid>0)负责往管道写(写的时候关闭读端),
	子进程(pid<0)负责读(读的时候关闭写端),假如从管道读不到数据就堵塞,
	

在内核中创立空间,不是文件,但是可以用write/read;
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main()
{
        int fd[2];
        char readbuf[128] = {0};
        int mark = pipe(fd);
        pid_t pid = fork();
        if(pid>0)
        {
                close(fd[0]);
                write(fd[1],"hello from father",strlen("hello from father"));
                wait(NULL);
        }
        if(pid == 0)
        {

                close(fd[1]);
                read(fd[0],readbuf,strlen("hello from father"));
                printf("%s\n",readbuf);
                exit(0);
        }
        return 0;
}
编程思想:父子进程同时运行,父亲打开写管道,写完等待子进程退出,假如父进程没写完
子进程就读不到数据阻塞住,所以一定是父进程先运行的
2、命名管道FIFO(全双工管道):
与无名管道不同的是:FIFO可以在无关进程间通信,且在文件系统中以文件形式存在
无名管道只适用于父子进程的通信,管道问文件名,只存在于内存中,文件中无文件名
一旦创建了一个FIFO就可以用一般的文件I/O进行操作
mkfifo("./file",0600) //当前路径下创建file管道文件,权限是可读可写
errno = EEXIST//管道创建失败,原因是已存在
perror("why");//打印创建失败的原因
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main()
{
        if((mkfifo("./file",0600) == -1) && errno != EEXIST)//如果管道创建失败且失败的原因不是已存在
        {
                printf("make fifo failed\n");
                perror("why");
        }
        return 0;
}
当open一个fifo时,是否设置非阻塞标志O_NONBLOCK的区别:
若没有指定(默认没有阻塞),只读open阻塞到其他进程为写而打开次fifo,只写open要阻塞到其他进程为读而打开次fifo。最好设置阻塞
即FIFO要读取的时候,只有当其他进程要给FIFO写入数据时,才会顺利往下进行。
FIFO适用于两个无关系进程间的通信
FIFO的双方收发:
//写入端代码
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
        char *writebuf = "hello world";
        int fd = open("./file",O_WRONLY);
        write(fd,writebuf,strlen(writebuf));
        printf("write success\n");
        close(fd);
        return 0;
}
//读取端代码
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
        char readbuf[32];
        if((mkfifo("./file",0600) == -1) && errno != EEXIST)//如果管道创建失败且
失败的原因不是已存在
        {
                printf("make fifo failed\n");
                perror("why");
        }
        int fd = open("./file",O_RDONLY);
        read(fd,readbuf,32);
        printf("read success\n");
        printf("read context:%s\n",readbuf);
        return 0;
}
先运行读再运行写
3、消息队列
创建后是在内核中创建消息队列,在内核中时不会消失的
(1)创建或打开消息队列:成功返回队列ID,失败返回-1
	int msgget(key_t key, int flag);//key 队列索引值,通过索引值找到队列;flag打开队列的方式(多用0)
	msgget(0x1234,IPC_CREAT|0777)
	
(2)发送消息:成功返回0,失败返回-1
	msgsnd(int msqid, const void *ptr, size_t size, int flag);
	
	msgid:由msgget函数返回的消息队列标识码
	*ptr:准备发送的消息
	size:发送的消息的长度(不包括消息类型的long int长整型)
	flag:默认为0
	
(3)读取消息:成功返回消息数据的长度,失败返回-1
	int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
	!!!这里的type属于队列类型,即要在发送时定义发送内容与类型为一个结构体
	
(4)控制消息队列:成功返回0,失败返回-1
	int msgctl(int msqid, int cmd, struct msqid_ds *buf);
	msgctl(msgID,IPC_RMID,NULL);//移除队列
消息队列的双方收发:两个进程的结构为发收、收发。
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>

struct msg
{
        long type;
        char text[128];
};
int main()
{
        struct msg readbuf;
        struct msg sendbuf = {888,"this is from send"};
        
        int msgID = msgget(0x1234,IPC_CREAT|0700);//确保得到同一个队列号

        msgsnd(msgID,&sendbuf,sizeof(sendbuf.text),0);
        printf("send success\n");
        msgrcv(msgID,&readbuf,sizeof(readbuf.text),998,0);
        printf("%s\n",readbuf.text);
        msgctl(msgID,IPC_RMID,NULL);

        return 0;
}
进程A创建888的队列号并且往里面发送数据,发送完成后等待998队列发送完消息然后读取


#include <stdio.h>
#include <string.h>
#include <sys/msg.h>

struct msg
{
        long type;
        char text[128];
};
int main()
{
        struct msg readbuf;
        struct msg sendbuf = {998,"this is from write"};
        
        int msgID = msgget(0x1234,IPC_CREAT|0700);//确保得到同一个队列号

        msgrcv(msgID,&readbuf,sizeof(readbuf.text),888,0);
        printf("%s\n",readbuf.text);
        msgsnd(msgID,&sendbuf,sizeof(sendbuf.text),0);
        printf("send success\n");

        return 0;
}
进程B创建998的队列号并且往里面发送数据,发送完成后等待888队列发送完消息然后读取
!!!注:这里的队列索引值一定要相同

前面把队列号强制为某个数,这样的做法显然不是很明智,可以这样做:
key_t key;//定义一个键值
key = ftok(".",'z');//获取当前队列号
printf("key is %x\n",key);
msgget(key,IPC_CREAT|0777)

当两个进程同时自动获取队列索引时,系统会自动分配成功的,最后一个消息队列读完要记得移除消息队列

4、共享内存
开发步骤:获取共享内存,连接共享内存,给共享内存写入数据,断开与共享内存的连接

(1)创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
	key_t key;
    key = ftok(".",1);
    
	int shmget(key_t key, size_t size, int flag);
	(1)生成键值
	(2)开辟的内存大小必须以M为基本单位
	(3)共享内存权限
	
(2)连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
	  void *shmat(int shm_id, const void *addr, int flag);//获取共享内存地址
	 (1)共享内存的ID
	 (2)一般写0,让系统自动分配共享内存的地址
	 (3)一般写0,内存可读可写
	 
	 给共享内存写入数据:
	 strcpy(shmaddr,"hwx")
	 
(3) 断开与共享内存的连接:成功返回0,失败返回-1
 	  int shmdt(void *addr); 
 	(1)共享内存连接成功后返回的指针
 	
(4)卸载共享内存:成功返回0,失败返回-1
 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
 (1)共享内存ID
 (2)IPC_RMID,一般写这个
 (3)不关心这个写0

ipcs -m查看共享内存号
ipsrm -m  共享内存ID:移除共享内存

5、信号量
	比如你正在看电视,这个时候外卖到了,你需要放下手边的工作去处理这个事情,这就是一个信号
	类似于软中断(模拟中断)。
	假如有两个进程,进程二给进程一一个信号,那么进程1对相应的信号做出一个应对,同样信号也有优先级,
	看优先处理哪个信号,类似中断。可用kill -l 查看信号的名字及序号。kill -9 信号ID杀死信号。

	例如:单片机和PC进行数据交互时,PC给单片机的串口一个信号,单片机的串口得知有信号过来,
	触发一个中断进而执行Pc命令,
	
	信号都是以SIG开头的,信号的处理有三种办法,忽略,捕捉,默认。
	kill -9 进程ID   杀死进程,使用信号编号和名字都可以
	kill  -l查看所有信号,10、12 SIGURE是可以用户自定义信号   共62个信号
	SIGINT:默认是结束进程的(ctrl+c)
	SIGKILL和SIGSTOP是不能被忽略和捕捉的
sighandler_t signal(int signum, sighandler_t handler)
第一个参数signum:表示要捕捉哪个信号。  
第二个参数handler:函数指针,当捕捉到这个信号时,执行信号处理函数
子进程杀死后,父进程不回收会造成僵尸进程

1、初级信号编程(信号不携带消息)
捕捉信号:
#include <signal.h>
#include <stdio.h>
void handler(int signum)//捕捉到信号时自动把信号代数送进去
{
        printf("get signum is %d\n",signum);
        printf("never quit\n");
}
int main()
{
        signal(SIGINT,SIG_IGN);//第一个参数是要忽略的信号,第二个参数是忽略宏定义
         signal(2,SIG_IGN);
        signal(SIGINT,handler);//捕捉到SIGNAL指令时,自动执行handler指令
        signal(SIGKILL,handler);
        while(1);
        return 0;
}


int kill(pid_t pid, int sig);
1、pid_t:指定信号的pid号
2、sig:信号的编号
自己封装的命令可以提到/bin下面使用,sudo   mv  t_kill  /bin
杀死信号:
两种方式完成
#include <signal.h>
#include <stdio.h>

int main(int argc,char* argv[3])//命令行的都是字符串形式
{
        int signum;
        int pid;
        char cmd[128];
        signum = atoi(argv[1]);//字符串转换为整型数的函数
        pid = atoi(argv[2]);
        printf("num = %d,pid = %d\n",signum,pid);
        sprintf(cmd,"kill -%d %d",signum,pid);//做出一个cmd指令,是kill xx xx型的,>参数是后面的
        //kill(pid,signum);//类似 kill  9  pid
        system(cmd);//让系统运行杀死指令
        printf("send ok\n");
        return 0;
}

alarm(seconds);闹钟信号
alarm(1);//取消闹钟


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

int main()
{
        int i;
        alarm(1);//到1s就终止进程,闹钟信号
        for(i=1;i>0;i++)
        {
                printf("%d\n",i);
        }
        return 0;
}

忽略信号:

	signal(SIGINT,SIG_IGN);//第一个参数是要忽略的信号,第二个参数是忽略宏定义
	即收到ctrl+c时,忽略此信号,SIG_IGN护理的宏定义

前面三种信号类似于只收到信号动作,比如敲门知道中断,而不知道这个敲门意味着什么。
发信号:kill
操作信号:signal()


2、高级信号编程(信号携带消息)
解决问题:
发送:用什么发送信号,信号如何携带消息
接受:用什么接受信号,如何读出消息
用sigaction来收信号,用sigqueqe来发送信号。
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
(1)第一个参数表示接受的信号。
(2)第二个参数表示收到这个信号想干嘛,需要构造结构体
(3)第三个参数表示备份,通常NULL

```c
//发送信号
#include <signal.h>
#include <stdio.h>
//       int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char **argv)
{
	int signum;
	int pid;
	signum = atoi(argv[1]);//信号代号,SIGUSR1代号是10
	pid = atoi(argv[2]);//要发送到的进程号
	
	union sigval value;
	value.sival_int = 100;//写入信号要携带的消息
	
	sigqueue(pid,signum,value);//给这个进程发送数据
	
	printf("%d done\n",getpid());
	return 0;
}
发送信号步骤:
1、构造联合体union sigval value;
2、联合体里面的value里面传输要发送的内容
3、pid:给哪个进程发信号  signum:给这个进程发哪个信号


//接收信号
开发流程:配置act结构体、配置处理函数
#include <signal.h>
#include <stdio.h>
void   handler (int signum, siginfo_t *info, void *context)
{
        printf("get signum = %d\n",signum);
        if(context != NULL)
        {
                printf("get data = %d\n",info->si_int);//这里是指针所以要用->,收到信号内容
                printf("get data = %d\n",info->si_value.sival_int);//同样是信号内容
                printf("from %d\n",info->si_pid);//发送者的pid
        }
}
int main()
{
        struct sigaction act;
        printf("pid = %d\n",getpid());
        
        act.sa_flags = SA_SIGINFO;//要收信号就把这个参数设置为SA_SIGINFO
        act.sa_sigaction = handler;//收到信号处理该函数
        
        sigaction(SIGUSR1,&act,NULL);//接收SIGUSR1信号,SIGUSR1代号是10,接收到信号放act里面解析
        
        while(1);
        return 0;
}


接收信号步骤:1、sigaction()收到SIGUSR1信号执行act,第三个参数用来备份,
			 2、要接收信号,必须把act构造为结构体,并且设置里面参数为
			 act.sa_flags = SA_SIGINFO;//要收信号就把这个参数设置为SA_SIGINFO
        	 act.sa_sigaction = handler;//收到信号处理该函数
        	 3、handler收到该信号先把该信号值打出来
        	 4、判断context内容,如果有内容就把info里面的数据打出来

先运行接收信号知道该进程的pid号,一直不退出;再运行发送信号,发给接收进程一个信号,
写入value信息。

一次仅一个进程使用的就是临界资源,如打印机
设想一个场景:有一个房间,有一把钥匙,当A拿走要是进入该房间时,B不能进入到该房间,
只有当A把钥匙放回,B才能进入该房间。
钥匙就叫信号量,房间就称为临界资源,共享内存就是一种临届资源,当处理A进程时,B进程不能被处理。
p操作:拿锁   V操作:放回锁

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
union semun
 {
       int              val;    /* 信号量个数 */
       struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
       unsigned short  *array;  /* Array for GETALL, SETALL */
       struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
};
void pGetKey(int id)//拿走锁,最终目的:锁数量-1
{
	struct sembuf set;
	set.sem_num=0;//对第0个锁进行操作
	set.sem_op=-1;//拿走锁,锁数量-1
	set.sem_flg=SEM_UNDO;//一般都用这个
	semop(id,&set,1);//
	printf("getKey\n");
}
void vPutKey(int id)//放回锁,最终目的:锁数量+1
{
	struct sembuf set;
	set.sem_num=0;//对第0个锁进行操作
	set.sem_op=1;//放回锁,锁数量+1
	set.sem_flg=SEM_UNDO;//一般都用这个
	semop(id,&set,1);//
	printf("putKey\n");
}
int main()
{
	key_t key;
	int semid;
	int pid;
	union semun initsem;
	key = ftok(".",2);
	semid = semget(key,1,IPC_CREAT|0666);//信号量创建成功,返回信号量ID
	
	initsem.val = 0;//初始化锁数量
	semctl(semid,0,SETVAL,initsem);//初始化信号量
	
	pid = fork();
	if(pid>0)
	{
		pGetKey(semid);
		printf("this is father\n");
		vPutKey(semid);
	}
	if(pid == 0)
	{
		printf("this is child\n");
		vPutKey(semid);
	}
	return 0;
}

首先对创建信号量,并且获取信号量ID,其次初始化信号量(锁为0),创建P,V操作,创建进程,
父进程先拿锁(锁数量为0,拿不到),只有等子进程放锁,父进程才能拿到,巧妙地设计了一个
让子进程先运行的案例。

对信号量的操作,唯一的操作也就是父子进程如何拿锁,P、V操作都是固定的构造好的。

四、多线程

多线程开发包含三种:线程、互斥锁、条件
(1)线程操作分为:创建、退出、等待
(2)互斥锁操作分为:创建、销毁、加锁、解锁
(3)条件操作有五种:创建、销毁、触发、等待、广播

一个线程死掉,整个进程死掉,多进程程序比多线程程序健壮
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,
启动一个**新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,
这是一种"昂贵"的多任务工作方式。而运行于一个进程中的**多个线程,它们彼此之间使用相同的地址空间,
共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间**,而且,**线程间彼此
切换所需的时间也远远小于进程间切换所需要的时间。
	不论是不是写时copy,都会重新开辟空间供子进程运行,而一个进程中多线程是共享进程的空间。
	同一进程的空间多线程进行共享。
	
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据
的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线
程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便
	线程间通信更加方便,进程间通信耗CPU。


pthread_self():线程ID号获取
getpid():进程ID号获取
1、pthread_create(pthread_t* thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg);
(1)创建的线程名字
(2)常常NULL
(3)线程执行的函数
(4)给该函数传参
	如果该函数参数不止一个,那就把该函数的参数做成一个结构体,把该结构体地址作为参数传进去。
创建成功返回0

2、pthread_join(pthread_t pthid, void **thread_return);
(1)等待的线程名字。
(2)thread_return 是一个传出参数,接收线程函数的返回值。类型(void **)
3、pthread_exit((void*)p);//线程退出,并且返回p的值为无类型

多线程空间共享代码验证:
注:进行编译时一定要链接 -lpthread库
#include <pthread.h>
#include <stdio.h>
int g_data = 0;
void *func1(void* arg)
{
        //static char* p = "t1 is run over";//必须要用static
        printf("t1:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
        printf("t1.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
        //pthread_exit((void*)p);
        while(1)
        {
                printf("t1:%d\n",g_data++);
                sleep(1);
        }
}

void *func2(void* arg)
{
        //static char* p = "t1 is run over";//必须要用static
        printf("t2:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
        printf("t2.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
        //pthread_exit((void*)p);
        while(1)
        {
                printf("t2:%d\n",g_data++);
                sleep(1);
        }
}
int main()
{
        int ret1;//定义线程返回标志
        int ret2;
        int param = 100;//给线程传参
        pthread_t t1;
        pthread_t t2;
        //      char* pret = NULL;
        ret1 = pthread_create(&t1,NULL,func1,(void *)&param);
        ret2 = pthread_create(&t2,NULL,func2,(void *)&param);
        if(ret1 == 0 && ret2 == 0)
        {
                printf("main: pthred is creat success\n");
        }
        printf("main: %ld\n",(unsigned long)pthread_self());//获取主函数线程号
        while(1)
        {
                printf("main:%d\n",g_data++);
                sleep(1);
        }
        pthread_join(t1,NULL);//pret是为了获取线程退出所返回的内容,无返回值则NULL,等待线程退出再往下执行,否则主进程直接退出,线程来不及运行就退
出。
        pthread_join(t2,NULL);
//      printf("main is run over,%s\n",pret);
        return 0;
}

互斥锁造成死锁:两个线程都有自己的锁,想在对方未解锁时还要拿到对方的锁就会造成死锁。

条件锁:t1只运行到g_data=3的时候
```c
pthread_cond_signal(&cond);//条件发生
pthread_cond_wait(&cond,&mutex);//运行等待处以后的代码

#include <pthread.h>
#include <stdio.h>
int g_data = 0;
pthread_mutex_t mutex;//创建互斥锁
pthread_cond_t cond;//创建条件线程
void *func1(void* arg)
{

	printf("t1:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
	printf("t1.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
	while(1)
	{
		pthread_cond_wait(&cond,&mutex);//等待上锁条件,条件满足才运行,无条件则阻塞,那t2便获得运行权限
		printf("========t1 run=======");
		printf("%d\n",g_data);
		g_data = 0;
		sleep(1);
	}
	
}
void *func2(void* arg)
{
	
	printf("t2:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t2线程号
	printf("t2.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
	while(1)//假设t2先运行
	{
		
		printf("t2:%d\n",g_data);
		pthread_mutex_lock(&mutex);
		g_data++;
		
		if(g_data == 3)
		{
			pthread_cond_signal(&cond);//(达到上锁条件)满足条件,转到func1里的wait
		}
		pthread_mutex_unlock(&mutex);
		sleep(1);//在这段时间里t1可与t2竞争获得锁权限
	}
}
int main()
{
	int ret;//定义线程返回标志
	int param = 100;//给线程传参
	
	pthread_t t1;
	pthread_t t2;
	
	pthread_mutex_init(&mutex,NULL);//初始化锁
	pthread_cond_init(&cond,NULL);//初始化条件锁
	
	ret = pthread_create(&t1,NULL,func1,(void *)&param);
	ret = pthread_create(&t2,NULL,func2,(void *)&param);

	pthread_join(t1,NULL);//pret是为了获取线程退出所返回的内容,无返回值则NULL,等待线程退出再往下执行,否则主进程直接退出,线程来不及运行就退出。
	pthread_join(t2,NULL);
	
	pthread_mutex_destroy(&mutex);//销毁锁
	return 0;
}

加入t1先运行,那么t1则阻塞,此时t2获取锁运行,当t2把参量运行到3时,t1达到运行条件开始运行,运行完参量归0,延时1秒,此时t2开始运行。

网络通信:
1、套接字
网络地址包括IP地址和端口号
多人聊天室:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

￴ㅤ￴￴ㅤ9527超级帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值