Linux文件/IO,应用编程

-----------------------应用框架结构------------------------------
• 应用编程框架介绍
信号:
多线程:(多核心CPU)
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 文件操作的一些系统API
什么是操作系统API?:
API其实就是一些函数,APP通过操作系统提供的API来使用硬件干活,而硬件如何干活,则就是驱动程序的工作了。
Linux一些常用的文件IO接口:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/************************
 *OPEN_API函数的FLAG测试*
 ************************/
int main(int argc, const char *argv[])
{
	int fd = -1;
	int ret = -1;
	char read_buf[100] = {0};
	char write_buf[100] = "my_loveianidn\nsaindisnafhabfa";
	
	//第一步:打开文件 
	//int open(const char *pathname, int flags); 返回一个文件描述符
	fd = open("test.txt", O_RDWR | O_APPEND | O_EXCL | O_CREAT, 0666);
	
	if (-1 == fd)
	{
		//printf("打开失败.\n");
		perror("打开失败");
		_exit(-1);
	}
	else
	{
		printf("打开成功,成功打开的文件操作符为[%d].\n", fd);
	}
	
#if 0
	//第二步:读取文件
	//ssize_t read(int fd, void *buf, size_t count);
	ret = read(fd, read_buf, 20);
	if(-1 == ret)
	{
		//printf("读取失败.\n");
		perror("读取失败");
		_exit(-1);
	}
	else
	{
		printf("读取成功,成功读取的大小为:[%d].\n", ret);
		printf("读取的内容为:[%s].\n", read_buf);
	}
#endif

#if 1
	//第三步:写入文件
	//ssize_t write(int fd, const void *buf, size_t count)
	ret = write(fd, write_buf, strlen(write_buf));
	if(-1 == ret)
	{
		//printf("写入失败.\n");
		perror("写入失败");
		_exit(-1);
	}	
	else
	{
		printf("写入成功,成功写入了[%d]字节.\n", ret);
	}
#endif
	
	//第四步:关闭文件
	close(fd);
	return 0;
}

open,read,write,close
Iseek,改变文件指针

操作文件的一般步骤:
(1)静态文件(2)动态文件
文件都是存放在块设备中的(INAND,SD)这些文件称为静态文件,当需要操作文件的时间,利用API(OPEN)来打开文件,此时操作系统采取的措施是,先将这个文件加载到内存中,然后给这个文件它的数据结构(方便读写),然后进行读写。这段在内存中的文件就是动态文件,当我们更改结束时,需要用CLOSE,将它加载同步回静态文件,所以这也是为什么不保存就不会发生改变的原因。

为什么要这样设计?
也许你要问为什么不直接在硬盘上进行操作呢?这是由于硬盘的一些特性决定的,我们知道硬盘的特点是容量大,但是操作不灵活,而内存(RAM)的特性,可以随机访问,且速度快,所以这样设计。

重要概念:文件描述符
文件描述符其实就是一个数字,用来表示区分,打开的文件在当前进程中的。他的生命周期就是当前进程。当你打开同时打开3个进程,操作系统怎么知道你需要操作的是哪个文件呢?文件描述符就是对这些打开的文件进行编号区分,便于操作。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------

• 一个文件读写的实例
fd->文件描述符
(1)open函数返回的是fd ,其余函数返回的是另外的ret,且参数O_RDWR(现在还不明白为什么这样)
(2)read函数返回的是ret,当成功时返回一个成功读取的字节数,当失败时,返回一个-1.
(3)write函数返回的是ret,当成功时返回一个成功读取的字节数,当失败时,返回一个-1.
(4)close函数返回的ret,一般不会出错,所以没加以判断,但应该判断,这是好习惯。
它的传参是fd.
-----------------------------------------------------------------------
------------------------------------------------------------------------------------

• open的flag详解
O_TRUNC:
加了这个flag,则若读取,就会置空本次读取的文件。
O_APPEND:
加了这个flag,若写入,则会在源文件后面,添加上本次写入的信息。
O_CREAT:
打开文件有一个问题,如果不存在这个文件,那么调用open打开,会报错。但有时间我们就需要向VIM直接打开并创建一个文件,用O_CREAT flag即可。
O_EXCL:
以上,万一创建的时间有一个文件和名字一样,那么现在再创建的时间会覆盖掉源文件,会产生恶劣影响,我们又需要像windowS那样的提示,不让我们创建,那么就需要 O_EXCL和O_CREAT合作使用了。
O_SYNC:
——阻塞式与非阻塞式:
当函数需要某项功能,它开始调用一些功能(比如蜂鸣器),然后这个时间有许多的程序都在调用这个功能,它有两个选择
(1)原地等待,指导前面的程序跑完,然后它开始使用蜂鸣器。(阻塞式)
(2)先遛了,然后待会没有人使用的时间在回来(非阻塞式)
异同:
阻塞式明显有个好处,那就是它一定可以结束完成后,再返回,所以它返回的结果是一定成功的。
非阻塞式,它不能带回一个确切的结果,但是它节约了时间。

O_NONBLOCK
特性:
很多时间我们需要调用系统的硬件驱动,而对硬件的操作的程序一般都是存放在缓冲区中的,因为频繁的操作硬件,会减少硬件的寿命。所以,这里的程序都是阻塞式的,但是有些时间,我们迫切需要使用到这个硬件,那么就需要O_NONBLOCK,来告知硬件系统,现在就开始使用硬件。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------

• 文件读写的一些细节
errno(错误信息编码)
perror(打印错误信息)
查询man手册确定该函数是否带errno
(主要查看RETURN VALUE)
write 和 read 的conut 和阻塞式
write 和 read 的conut我们在读取或者写入时是无法确切知道的,因为我们也不知道需要写入或者读写到底多大,这里牵扯出一个问题
文件里有20b的内容,而我们read打开时,需要读取的内容是30b,这里正好是阻塞式的,然后程序就会在原地等待剩下的10b,这样其实没有必要,且效率很低。

所以系统把文件IO封装成标准IO (fOPEN)
用标准IO操作文件,然后存在标准IO的BUF中,然后等待一个合适的大小,一起存入操作系统内的buf中。这样就大大提高了效率。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• Linux系统如何管理文件
硬盘中的静态文件和inode(i节点)
通过之前的学习我们知道什么是静态文件,也就是存放在硬盘中的文件,那么,一般都是怎么读取这些文件的呢?静态文件的管理分为两个部分
(1)硬盘中的管理项
管理项就相当于索引点,系统通过这些索引,来找到相关文件的内容(inode),其实就是一个对应数据结构的结构体,它里面存放着一些文件的信息,当需要哪个文件的时间,我们就通过检索这个结构体,与需要的文件相匹配,由此找到文件。
(2)硬盘中的存储项
这个就不说了,就是一个一个的块设备,来存储信息的。

特性:格式化U盘,有两种办法
(1):快速格式化,这个格式化速度很快,其实就是只删除了文件管理项。也就是索引。所以已经删除的信息是可以找回的。
(2):低级格式化,这个格式化速度很慢(亲测),这个才是彻底删除信息,也就是将信息从内存中抹去。

内存中的V节点(vnode)
我们知道文件分为动态和静态的,所以这里的操作都是在内存中,自然就是动态的。当某个进程需要某个文件,则将它从硬盘中将它的文件管理项读出,并且利用进程管理,去管理它,此时进程会建立一个v节点,也就是这个文件的数据结构,一个V节点中存放了这个文件的fd,以及各种信息,所以就可以对这个文件进行各种操作了。

文件与流的概念:
流:(stream)其实编写的程序,比如,续接两个字符串,我们就是通过循环,对一个一个的字符进行操作对比,以及拼接。文件也是如此,文件的操作不可能是一块一块的,其实归根结底也是一个一个的字符进行操作。对一个一个字符的读写,也就是形成了流的概念。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• lseek详解
(1)文件指针
RDWR隐式的更改文件指针,lSEEK显示的更改文件指针。一开始打开文件时,都是默认到文件开头的,RD,WR都是向后偏移一个。
lSEEK,是指定文件指针的偏移量;
off_t lseek(int fd, off_t offset, int whence);
SEEK_SET 从文件头
SEEK_CUR 从文件中
SEEK_END 从文件尾
(1)利用lseek计算文件大小
lseek(fd, 0 SEEK_END)直接把文件指针移动到文件尾所的到的偏移量就是文件大小。
(2)利用lseek构造空洞文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/************************
 *   计算文件大小小程序 *
 ************************/
 //iseek:移动文件指针到文件头,文件中,文件尾,返回移动的偏移量,可以利用这个特性计算文件大小
int cal_lenFun(const char *pathname);
int main(int argc, const char *argv[])
{
	if (argc != 2)
	{
		printf("—————————————————————————————————————————————\n");
		printf("请正确输入,请在程序后加索要计算的文件名.\n");
		printf("譬如:");
		printf("%s pathname.\n", argv[0]);
		printf("—————————————————————————————————————————————\n");
		_exit(-1);
	}
	
	
	printf("文件的大小为:%d.\n", cal_lenFun(argv[1]));
	
	return 0;
}

int cal_lenFun(const char *pathname)
{
	int ret = -1;
	int fd = -1;
	//打开文件
	//int open(const char *pathname, int flags, mode_t mode);
	fd = open(pathname, O_RDWR | O_CREAT, 0666);
	if (-1 == fd)
	{
		perror("打开错误");
		return -1;
	}
	
	//计算长度
	//off_t lseek(int fd, off_t offset, int whence);
	ret = lseek(fd, 0, SEEK_END);
	if (-1 == ret)
	{
		perror("lseek error");
		return -1;
	}
	
	
	return ret;
	
}


-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 打开多个文件(文件共享)
两个文件指针(两种情况)
1.读取
(1)分别读取
(2)续借读取
同时打开两个文件进行读写
对两个文件进行读取,如图所示,文件指针是由不同进程的文件管理表管理的,所以拥有两个不同的文件指针。所以是分别读取的。
2.写入
那么在写入的时间,由于是两个分离的文件指针,所以当fd1写入后,fd2写入则会覆盖掉原来的文件。
O_APPEND标志:
加了这个标志就可以解决覆盖的问题。
原子特性:
O_APPEND实现的原理就是原子特性的,原子特性就是当一个进程开始工作以后,除非到它结束,否则就不能停止。
(1)同一个进程多次OPEN文件

(2)多个进程分别OPEN文件
_____dup系统:
dup和重定位:
dup系统调用,就是重新复制一个fd,之前发现,打开文件后fd总是从3开始,那么012去哪里呢?
0-----stdin
1-----stdout
2-----stderr
所以当我们close(1)然后再用dup去复制一个fd那么系统就会自动分配fd = 1给我们用,这时间,我们再往输出上的信息就会重定位到我们当前fd=1的文件里了。
(tips)fd的管理其实是一个数组。所以数值上是连续的切不可跳跃的。
_____dup2系统:
dup2可以指定一个确切的fd,函数原型如下。
int dup2(int oldfd, int newfd);
所以便可以轻松实现上面的输出重定位
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• FCNTL接口
int fcntl(int fd, int cmd, ... /* arg */ );
可以实现仿制dum2,dum2的缺点在于需要指定一个Fd,而万一你指定的这个fd正好在被使用,那么就会产生错误,而fcntl则不会产生这种问题,他会分配一个最靠近且不在占用的Fd给你。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/************************
 *	fcntl函数的使用
 ************************/
//利用fcntl拷贝一个文件描述符,比较安全
#define FILENAME	"1.txt"
int main(int argc, const char *argv[])
{
	//打开文件
	int fd1 = -1;
	int fd2 = -1;
	fd1 = open(FILENAME, O_RDWR | O_CREAT | O_TRUNC, 0666);
	if (-1 == fd1)
	{
		perror("open");
		_exit(-1);
	}
	printf("成功打开文件fd1:%d.\n", fd1);
	//close(1); //关闭1  然后复制一个fd,则会分配1
	fd2 = fcntl(fd1, F_DUPFD, 1); 
	//(fd, 指令flag, 预计得到的fd,如果占用就选择一个没被使用的,而且是最接近的)
	if (-1 == fd1)
	{
		perror("fcntl");
		_exit(-1);
	}
	printf("11111.\n");
	printf("成功打开文件fd2:%d.\n", fd2);
	
#if 0
	while(1)
	{
		write(fd1, "aa", 2);
		sleep(1);
		write(fd2, "cc", 2);
	}
#endif
	
	return 0;
}


-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 标准输入输出
Fopen Fclose Fwrite Fread Ffulsh Fseek
Fread r+
Fwrite w+
在fclose后一定要将fp指针归0.
fp = NULL;
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define FILENAME	"1.txt"
/************************
 *   标准输入输出	    *
 ************************/
int main(int argc, const char *argv[])
{
	FILE *fp = NULL;
	char read_buf[100] = {0};
	size_t len = 0;
#if 0
	//FILE *fopen(const char *path, const char *mode);
	//打开(目的读取)文件
	fp = fopen(FILENAME, "r+");
	if (NULL == fp)
	{
		perror("Fopen");
		_exit(-1);
	}
	printf("成功打开文件fp:%p\n", fp);
	
	//读取文件
	//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
	memset(read_buf, 0,   sizeof(read_buf));
	len = fread(read_buf, sizeof(read_buf[0]), 
						  sizeof(read_buf)/sizeof(read_buf[0]), fp);
	printf("fread is [%s].\n", read_buf);
	printf("lenoffile:[%d].\n", len*sizeof(read_buf[0]));
#endif
	//FILE *fopen(const char *path, const char *mode);
	//打开(目的读取)文件
	fp = fopen(FILENAME, "w+");
	char write_buf[10] = "CCAAADWe";
	int ret = -1;
	if (NULL == fp)
	{
		perror("Fopen");
		_exit(-1);
	}
	printf("成功打开文件fp:%p\n", fp);
	//写入文件
	//size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
	len = fwrite(write_buf, 1, 10, fp);
	if (-1 == len)
	{
		perror("fwrite");
		_exit(-1);
	}
	printf("lenoffile:[%d].\n", len);
	ret = fclose(fp);
	if(0 == ret)
		printf("okokok.\n");
	fp = NULL;
	printf("fp=%p.\n", fp);
	return 0;
}


-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 文件属性
文件属性的获取
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
stat是获取从一个路径获取文件属性,所以是从硬盘里读取
Fstas是从fd获取,所以是从内存中获取,是从已打开文件中获取,所以速度会快写。
lstat是获取文件权限,区别于上面两个的是,上面两个获取的文件属性如果是链接文件,则会自动追踪到源文件,而Lstat则不会追踪,它获取的就是当前文件的属性。

一些宏和St_mode
如果测试权限是如上可读的那么就是返回1,利用了这样一个位与的操作
文件权限不是谁创建的它而是谁运行的它
第一组是属主,第二组是属主所在的组,第三组是其他
指令:
(vvvvvvvvip)_________________________________________
权限测试API ——————access
当前用户对于测试文件的权限。
修改权限API—————— chomd
chown root filename修改文件的属主
chgrp root filename文件所在的组
umask 0000为补
读取目录文件API—————— opendir 、readdir
重入版本
具体使用请看代码。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 关于时间的概念
GMT时间:格林尼治时间
UTC时间:原子钟时间
点时间:具体某点的时间(RTC)
段时间:点时间 -点时间(定时器)
电脑在开机时会去读取一个RTC,然后根据系统的节拍,给jiffies加数,算出一个距离1970的时间节点的秒数、
API实战:
time :获取计算机元年到现在的一共的秒数
ctime:通过time获取的秒数,的到一个字符型的时间
gmtime:UTC时间
localtime:当地时间
asctime:和ctime一样
strftime:用户定义时间格式

随机数
随机数API rand
srand(time(NULL));设置随机数种子
rand()%n0~n-1 的随机数
linux内核中产生一个半真正意义上的随机数种子,依靠一些随机产生的动作,比如,用户操作鼠标,键盘等,依靠这些作为随机数种子,即可产生一个意义上的随机数。
proc文件系统和sys系统
/proc/ /sys/
这快文件系统是虚拟的,只有动态内存,没有静态内存,一般都用于调试。

-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 进程
main函数的返回值返回给谁,它被谁调用??
main函数被系统调用,其实是在运行阶段,链接
argc argv 怎么传递参数的?
通过shell采集到参数,然后通过系统,加载器加载到代码中运行
atexit :当程序结束时,运行时的代码,栈原理运行,先入后出。
程序运行有两种方式结束:
(1)正常结束
(2)强制结束
return 0 exit :
_exit:扛把子。直接退出,不运行系统结束的时的函数。
进程

环境变量:
许多程序,在本机开发环境中可以执行,一换机器就不能执行,这大部分都是环境变量的问题。我们可以通过export打印出环境变量。
在程序中也可以使用environ变量去打印。
environ是一个字符串数组,所以应该是一个二重指针。
getenv 函数获取一个环境变量
虚拟地址
虚拟地址就是,每个进程都分配了4G的逻辑内存,其中0-1G为OS,1-4G为应用使用内存,然而实际它是操作系统分配的虚拟地址,当每个进程需要一定内存,就去物理内存上去获取,这有点像银行储蓄。

虚拟地址的作用?
(1)进程隔离,因为进程是独立的虚拟地址,所以相互独立。
(2)提供多进程共同运行。

进程进入:
ps -aux :向终端打印所有的进程
进程控制块:PCB 就是一个数据结构,用来管理新进的一些信息。
进程ID:getpid getppid
多进程的调度原理:进程间相互调度,所以在微观上是串行的,而在宏观上是并行的。合理的进程调度,提高效率。

fork函数:
fork函数会返回两个进程,一个父进程,一个子进程,内部实现的原理是,当需要产生一个新的进程时,就从老的拷贝一份
子进程:
子进程和父进程的关系 子进程有自己的PCB 子进程被内核同等调度。
父子进程对文件的操作:
(1)打开同一个文件, 接续的,因为同一个文件打开,文件指针会置位。
(2)分别打开同一个文件,分别的,因为两个进程有两个文件管理表,所以文件指针时分别的,需要加O_APPEND
子进程的最终目的就是实现独立的运行程序

进程的诞生和消亡:
(1)进程0,1(init)
(2)fork
(3)vfork
僵尸进程:
父进程会fork出来一个子进程,当子进程运行结束时,操作系统会回收子进程的资源,但是剩下8K的管理内存没回收,此为僵尸进程。其实就是子进程运行完毕没被父进程回收。
可以显示的用wait/waitpid回收。
孤儿进程:
父进程先死了,所以没人给正在运行的子进程回收,这是系统会自动把这个进程分配init进程(进程1)做为它的父进程(找个老爸)。
wait:
(请阐述wait这个系统调用的原理)
父进程会利用一个系统调用显示的回收子进程,这个系统调用就是wait,当需要回收是会遇到两个问题
(1)子进程先运行,然后运行结束,父进程回收子进程,正常情况
(2)父进程先运行,子进程还没运行结束,那么他需要等待子进程结束,这就是wait系统调用的阻塞原理,他会把父进程阻塞,然后等待子进程的结束。
那么当父进程没有子进程的时间,wait会怎么样呢?当然不会一直阻塞在那里。它会以错误形式返回一个-1;
wait的参数
原型如下 pid_t wait(int *status);
它的输入参数中有一个输出型参数(没加const),这个status,就会返回回收的子进程的状态,结合下面几个宏定义。
WIFEXITED
-> 是不是正常终止,正常终止返回1,否则返回0
WEXITSTATUS
-> 返回子进程的结束状态,也就是返回值。
WIFSIGNALED
-> 是不是非正常终止(信号)正常终止返回1,否则返回0
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

/************************
 *	wait回收进程和status
 ************************/
 //wait,默认阻塞式回收子进程,通过传入的status,与响应的宏进程比较可以获取到进程的响应信息
int main(int argc, const char *argv[])
{
	
	pid_t pid = -1;
	pid = fork();
	int status;
	if (pid == 0)
	{
		printf("这是子进程%d.\n", getpid());
		return 120;
	}
	if (pid > 0)
	{
		printf("这是父进程%d.\n", getpid());
		pid = wait(&status);
		printf("回收的进程号是%d.\n", pid);
	}
	if (WIFEXITED(status))
	{
		printf("\n程序正常终止.\n");
	}
	
	if (WIFSIGNALED(status))
		printf("程序非正常终止.\n");
	
	printf("程序的返回状态为%d.\n", WEXITSTATUS(status));
	return 0;
}


waitpid:
原型如下:pid_t waitpid(pid_t pid, int *status, int options);
可以指定的回收某个进程,可以选择是否采用阻塞非阻塞模式。
(1)waitpid(-1, &status, 0)
-1:不指定pid,收回任意满足条件的子进程,0,阻塞式,其实就和wait( )功能基本一样。
(2)waitpid(-1, &status, WNOHANG);
不固定某个pid,使用非阻塞式,也有两种情况
1.父进程先执行,然后调用wait,子进程还没执行完,那么直接返回0.
2.给父进程加了sleep,在父进程休眠的这段时间,子进程运行结束,然后父进程唤醒,回收子进程。
(3)固定某个不存在的pid,使用阻塞式
因为waitpid可以指定回收某个进程号,所以当我们指定一个不存在的进程号,而且采用阻塞式的,那么它就会返回一个错误,因为操作系统的设计是很合理的,不可能让程序在原地卡死。

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

/************************
 *	waitpid与阻塞非阻塞
 ************************/
 //waitpid可以用户自定义阻塞与非阻塞式回收子进程,可以指定或不指定(-1)回收子进程;
int main(int argc, const char *argv[])
{
	pid_t pid = -1;
	pid = fork();
	int status;
	if (pid == 0)
	{
		printf("子进程:%d.\n ", getpid());
	}
	if (pid > 0)
	{
		
#if 0
		//(1)不固定某个pid,切使用默认项,就是阻塞式的
		printf("父进程:%d.\n", getpid());
		pid = waitpid(-1, &status, 0);
		printf("被回收的进程为:%d.\n", pid);
#endif

#if 0	
		sleep(1);
		//(2)不固定某个pid,使用非阻塞式
		printf("父进程:%d.\n", getpid());
		pid = waitpid(-1, &status, WNOHANG);
		printf("被回收的进程为:%d.\n", pid);
#endif

#if 1	
		//sleep(1);
		//(3)固定某个不存在的pid,使用阻塞式
		printf("父进程:%d.\n", getpid());
		pid = waitpid(pid+3, &status, 0);
		printf("被回收的进程为:%d.\n", pid);
#endif
		
	}
	
	return 0;
}


exec族函数:
(请阐述exec族函数调用的原理)
通过传参,指定要执行程序的路径,然后执行程序
export:终端显示系统的环境变量
echo &PATH 。。。。
execl,直接加参数,参数后要加NULL
execv,通过定义一个字符串数组指针,要加NULL
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

/************************
 *	execl,execv,调用函数
 ************************/
 //exec族函数:用来在进程中调用一个已经写好的程序 v就是传参不太一样
int main(int argc, const char *argv[])
{
	pid_t pid;
	pid = fork();
	if (pid > 0)
	{
		printf("父进程进程号:%d.\n", getpid());
	}
	
	if (pid == 0)
	{
		printf("子进程进程号:%d.\n", getpid());
		/*
		char *const argv[] = {"ls", "-la", NULL};
		execv("/bin/ls", argv);
		*/
		char *const argv[] = {NULL};
		execv("./exe", argv);
	}
	
	

	return 0;
}


execlp,execvp,
如果不加p,那么让找不到给定路径的程序时,则会返回错误值,加了p以后如果找不到就回去系统默认添加的PATH里去找。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

/************************
 *	execlp,execvp,调用函数
 ************************/
 //P:exec不加P,就只会在指定目录查找一次,没有则返回错误,带P,还会到系统默认的目录查找一次。PATH的意思。
int main(int argc, const char *argv[])
{
	pid_t pid = -1;
	pid = fork();
	if (pid > 0)
	{
		printf("父进程PID:%d.\n", getpid());
	}
	if (pid == 0)
	{
		char *const arg[] = {"ls", "-la", NULL};
		printf("子进程:.\n");
		execvp("ls", arg);
	}
	
	return 0;
}


execle,execve,
即多传一个参数,环境变量

进程的状态和system函数
(1)进程的五种状态:(请阐述进程运行的各种状态)
1.就绪态具备一切运行条件,等待CPU调度
2.运行态正在占用CPU
3.僵尸态运行结束,但还未被回收
4.等待态 (浅度休眠)(深度休眠)
浅度休眠,是正在等待某个条件,或者某个条件,但是可以通过信号去唤醒
深度睡眠,是一定需要某个条件,等待资源的到来以后才会唤醒(比如文件要读写,但是硬盘被占用,他就需要硬盘的占用结束,然后才能进行工作,这是硬盘占用结束就是等到了资源,这是间程序就会唤醒)
5.暂停态收到信号,暂时停止,但不是真正的停止。真正的停止是僵尸态。
(2)各个进程的状态图


system函数
(1)system是原子操作的,原子操作就是一种不可打断的操作,他的执行一定要结束,所以会长时间占用CPU.这种操作可以避免竞争状态的产生
与之相反,fork+exec就不是原子的,当我们不需要原子操作时,就可以用fork+exec。
(2)用system函数使用ls命令
int system(const char *command);
直接加入要执行的PATH名即可

进程关系
(1)无关系
(2)父子关系
(3)进程组(group)
许多进程不好管理,所以需要一个群体管理,便于某些进程有相互需要时的作用。
(4)会话(session)
就是进程组的集群。

守护进程
查看进程的命令
(1)ps -ajx
(2)ps -aux
守护进程:(请阐述守护进程的基本特性)
(daemon)守护进程是一般以d结尾,与控制台脱离的后台运行程序,它都是在后台运行,一般用于服务器程序。长期运行
1、syslogd
系统日志守护进程
2、cron
操作系统中时间管理, 比如linux中的需要定时程序。
自己编写守护进程
任何程序都能变成守护进程,主要是看它有没有必要。

编程实现守护进程的步骤:
(1):子进程等待父进程退出,自己独立
(2):子进程使用setsid建立一个新的会话期,脱离控制台
函数 setsid
(3):调用chdir将工作目录修改到/用户主目录下
函数chdir
(4):将umask设置成0,使得文件的权限最大
函数umask
(5):关闭所有文件描述符,这里因为要动态获取一个文件描述符的最大值,所以需要调用sysconf
函数sysconf ,可以获取系统的参数
(6):将文件 0 1 2 定位到/dev/null 垃圾堆。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
void creat_daemon(void);
/************************
 *	守护进程的编写制作  *
 ************************/
 //守护进程就是一个后台运行的进程,代码实现如下:
int main(int argc, const char *argv[])
{
	creat_daemon();
	while (1)
	{
		printf("im runing .\n");
	}

	return 0;
}

void creat_daemon(void)
{
	//子进程等待父进程结束
	pid_t pid = -1;
	pid = fork();
	if (pid < 0)
	{
		perror("fork");
		exit(-1);
	}
	if (pid > 0)
	{
		//这里就是父进程了,直接退出
		exit(0);
	}

	if (pid ==0 )
	{
		//下面就是子进程

		//(1)调用setsid,建立一个会话期使其与控制台脱离    
		//原型:pid_t setsid(void);
		pid = setsid();
		if (-1 == pid)
		{
			perror("setsid");

		}
		//(2)将用户工作目录修改到 /目录下,保证安全,
		chdir("/");

		//(3)将umask设置为0
		umask(0);

		//(4)关闭所有文件描述符,因为系统不同所以文件描述符的大小也不定,这里我们调用一个函数去动态获取这个值
		int cnt = sysconf(_SC_OPEN_MAX);
		int i = 0;
		for (i=0; i<cnt; i++)
		{
			close(i);
		}

		//(5)将0,1,2重定位到/dev/null
		open("/dev/null", O_RDWR);
		open("/dev/null", O_RDWR);
		open("/dev/null", O_RDWR);

	}
	return ;
}


利用openlog,syslog,closelog将调试信息
(请阐述syslog函数的工作原理)
linux下有一个后台守护进程syslogd,当我们需要系统信息时,syslogd会和syslog,之间开辟一条通道去传输,由此实现了syslog记录日志信息的功能。
这种服务性质的原理在操作系统中有很多
log日志信息在/var/log/syslog下查看
#include <stdio.h>
#include <syslog.h>
/************************
 *	syslog调试信息      *
 ************************/
 //往日志信息里输入调试信息
int main(int argc, const char *argv[])
{
	//(1)void openlog(const char *ident, int option, int facility);
	openlog("a.out", LOG_PID | LOG_CONS, LOG_USER);
	//上面这个a.out,其实就是你指定一个标号,用来查询日志信息。可以任意修改
	
	//(2)void syslog(int priority, const char *format, ...);
	syslog(LOG_INFO, "This is syslog for a.outlog");
	syslog(LOG_INFO, "This is syslog for a.outlog");
	syslog(LOG_INFO, "This is syslog for a.outlog");
	//(3)void closelog(void);
	closelog();
	
	return 0;
}



让程序单一运行:
原理:就是在程序运行是创建一个文件,然后当有进程要运行时去检测这个文件在不在,如果存在,就表示进程正在运行,否则就不在运行;
利用的函数
注意创建文件的目录。如果在根目录下,那么权限可能有问题。
(1)open()O_CREAT, O_EXEC(如果文件存在则会报错,errno会被置位,可以通过errno判断)
(2)atexit需要在结束时清理掉创建的文件
(3)remove删除文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
/************************
 * 让程序单次运行的技巧 *
 ************************/
 //原理:当程序执行时创建一个怪异的文件,当程序再次执行时去检查这个文件是否存在,存在则禁止执行,当程序结束则删除这个文件。
 void delay_file(void);
#define FILE_NAME	"/var/my_singel" //注意,在根目录下的权限,一般是无法创建文件的所以可能无法执行创建文件操作
int main(int argc, const char *argv[])
{
	int fd = -1;
	fd = open(FILE_NAME, O_RDWR | O_TRUNC | O_CREAT | O_EXCL, 0666);
	if (fd < 0)
	{
		if (errno == EEXIST)
		{
			printf("请勿重复运行.\n");
			return -1;
		}
	}
	
	printf("打开成功.\n");
	
	atexit(delay_file);
	
	int i = 20;
	while (i-->0)
	{
		printf("I am runing ....\n");
		sleep(1);
	}
	
	
	
	
	
	return 0;
}

void delay_file(void)
{
	remove(FILE_NAME);
}



LINUX下IPC(进程间通信)
管道 (无名管道):
(1)原理:就是在内核中开辟一段空间(pipe),然后利用子进程继承父进程的文件描述符的特性,然后进行读写,但是通信方式是半双工的,就是同时智能实现单边通信(pipe)创建后,会返回两个文件描述符,分别读写。
(父进程fork子进程后,子进程继承父进程的管道fd,然后进行通信,发送端、接收端同时只能一个)

(2)缺点:
只是适用于父子进程间通信。



如图则是双工的实现

管道 (有名管道):fifo
通过一个指定的文件名,利用mkfifo,利用open分别获取这个文件的fd,然后一个读一个写。

system V IPC
(1)消息队列(fifo)
类似于链表先入先出,进程间通信时通过建立一个消息队列,一个进程发,一个进程从另一头读

(2)信号量
有点类似于编程思维中的flg,标号,其实信号量就是一个变量,当信号量为特定数时开始发送信号,信号量为特定数时开始接收信号。
(3)共享内存
linux下,前面两种通信速率都比较慢,而共享内存的通信办法就比较快速,这里是通过在内核中开辟一块内存,进程1将自己需要和进程2通信的信息放在这块内存中,进程2去读取即可,这里的内存和进程1,2之间有这虚拟映射,就避免了给进程2拷贝的副作用(如果不用共享内存则通信的话就需要拷贝)
举个栗子:
进程1是摄像头程序,它需要将拍的照片,传送给进程2处理压缩、编解码,那么有两种办法
(1)拷一份给他
(2)就是用上面的共享内存,但是共享内存有一个问题,万一进程1还没往共享内存里放完,进程2就开始读了,怎么办?这里就又需要信号的配合了,其实许多硬件都有这样的特性,共享内存有这广泛的使用。


信号
(1)SIGINT 2
Ctrl+C时OS送给前台进程组中每个进程
(2)SIGABRT 6
调用abort函数,进程异常终止
(3)SIGPOLL SIGIO 8
指示一个异步IO事件,在高级IO中提及
(4)SIGKILL 9
杀死进程的终极办法
(5)SIGSEGV 11
无效存储访问时OS发出该信号
(6)SIGPIPE 13 涉及管道和socket
(7)SIGALRM 14 涉及alarm函数的实现
(8)SIGTERM 15
kill命令发送的OS默认终止信号
(9)SIGCHLD 17
子进程终止或停止时OS向其父进程发此信号
(10)SIGUSR1 10
用户自定义信号,作用和意义由应用自己定义
SIGUSR2 12

两个函数
(1)signal
(2)sigactonal 很类似,
(3)alarm 定时闹钟,同时操作系统只能响应一个alarm,所以alarm会覆盖前面。
(4)pause 将cpu挂起
//宏的定义在/usr/include/i386-linux-gnu/bits/signum.





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值