除去fork()函数和返回的值不一样,父进程的返回值为子进程id,子进程返回值为0,可以根据此返回值区分这2个不同进程。其余代码在父子进程中都是一样的,除去在fork结果判断代码中。当父进程先结束后,子进程成为孤儿进程,谁init=1的进程为父进程,此时子进程由init=1进程启动管理。子进程在fork函数中就产生了,父进程中全局变量局部变量内存分配等资源都会copy到子进程中。文件描述符fd也会复制,但仍是同一个fd,并且文件信息仍会保留,指针移动等情况,两个进程共用一份,因为文件并不会复制,而有关此文件的信息是这两个进程共享的。子进程先结束,子进程给父进程发一个SIGCHLD,父进程回收子进程相关资源。父进程先结束,子进程成孤儿进程,孤儿认 init 进程(pid = 1)为自己的父进程,子进程结束时,会向init进程发送SIGCHLD信号,让init回收资源。子进程先结束,向父进程发信号SIGCHLD,但是父进程由于处于某种状态而没有收到信号,但是子进程已经结束,此时在系统的进程树中记录着子进程,这个子进程就是僵尸进程(其实是一个已经结束的进程,不存在的进程),孤儿进程是正常现象,不会对程序运行和性能等产生影响,所有孤儿进程都会认init为自己的父进程,init是孤儿院。进程:正在运行中的进程和可运行的进程。可运行的是进程指在等待队列中等待CPU资源的进程。在shell下执行一命令,启动一个进程,这个进程就是shell进程的子进程。
在一个进程用fork函数复制一份自己,复制出的那个进程就是这个进程的子进程,它会在复制进程的位置开始运行
这两个父子进程唯一不同是fork函数的返回值不同
返回>0父进程,返回的是子进程的pid ==0子进程 <0出错
复制文件描述符时,复制的仅仅是文件描述符本身,没有复制文件表
vfork和fork一样,不会复制进程内存空间,启动的是一个空白进程,专门为了在子进程中调用exec系列函数,保证子进程先运行。等待子进程的目的是为了获取子进程的返回值。如何获得返回值:通过给wait传参的方式获取返回值,返回值信息比较多,所以要通过一些宏函数把我们需要的值分离出来,waitpid()等待指定进程结束。
找孪生素数:比如 11 13 17 19等
#include <unistd.h>
#include <stdio.h>
#include <strings.h>
int isprime(int x)//return 1 if is x is prime,return 0 if not prime
{
int i;
for(i=2;i<x/2+1;i++) {if(x%i == 0) return 0;}
return 1;
}
int main()
{
int i=0;
int a[20];int sz=0;
bzero(a,20);
for(i=2;i<100;i++)//找100以内的孪生素数
{if(isprime(i)==1) a[sz++]=i;}
pid_t res = fork();
if(res>0){}//parent process
else if(res == 0)
{
int cnt=0;
for(cnt=0;cnt<sz;cnt++)
{
if(a[cnt]==0) return;
if(a[cnt+1]-a[cnt] ==2) printf("%d %d\n",a[cnt],a[cnt+1]);
}
}
else {}//fork() error
}
2.wait(&status);
会阻塞父进程执行,直到子进程结束,并把子进程返回状态返回到值status中,子进程中的exit(n),中的n就是子进程在 WIFEXITED(status) 为真(代表子进程正常退出)时的WEXITSTATUS(status)。waitpid(res, &status, 0)=wait(&status);如果父进程又子进程的退出状态不需要,而只是等待子进程结束,可以将有status的项设为null. alarm()函数返回上一个设定的alarm不剩余的值。
3. signal(signum,sig_handler)
信号是进程级别的,发信号其实就是进程发给进程的。信号是一个软件中断技术,信号会打断正常程序的执行。对信号处理signal(signum,sig_handler),中的sig_handler如果为SIG_IGN表示忽略此信号,SIG_DFL表示对此信号默认操作。进程可以接受,处理信号,处理信号时,信号会中断正常程序的执行,不可重入函数的调用需要注意:1.在正常中正在访问全局(静态)变量,而信号处理函数也访问这个变量,可能会出问题。2.正在执行一此系统调用(malloc free)在信号函数中也做同样的事,可能会出问题。 注意:信号处理函数尽量简单,一般都是处理一些错误,或者处理高优先级的突发情况,进程处理的信号越多,问题越复杂。 CTRL+C 发送的是SIGINT信号 CTRL+\ SIGQUIT信号 CTRL+Z SIGSTOP信号。(kill -l查看所有信号)
4.system("ls -l");
system的参数为一个shell命令,直接将shell命令写到里面就可以。或者system("./2");也可以启动一个可执行文件2,system的内部实现原理是先fork一个子进程,用此子进程调用exec族函数来执行命令的。
5.execl族函数:
execl(const char *path,const char *arg...):path表示执行的文件名或者路径,如:execl("2","./2",NULL);或者execl("bin/ls","ls","-l",NULL);如果后面要传递参数,直接传参数即可。execlp("./2","2",NULL);execlp("ls","ls","-l",NULL);此执行的文件名要在当前环境变量下配置,配置方法:先查看当前环境变量:echo $PATH. 配置:export PATH=$PATH:. 。execle: execle("./2","2",NULL,NULL). execv: char * argv[]={"./2",NULL};execv("./2",NULL),如果有要传递的参数,直接写到argv数组中。execvp:execvp("2",NULL);或者:char * argv[]={"ls","-l",NULL};execvp("ls",argv);
exec系列函数:
l : 参数是可变长的
v : 参数是字符串数组
p : 通过PATH环境变量找你要执行的命令文件
e : 要求传入环境表
注意: 1.命令本身也是命令行参数,而且是第一个命令行参数,一定要在参数中
2.参数表不管是变长的,还是数组的,最后一个参数一定是NULL
3.exec是将一个程序调入当前进程,覆盖当前进程的所有数据(代码 全局 栈 堆)所以没有创建新的进程
6.打印一个目录下所有目录和文件:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
void ptdir(char * dirname,int de)//此代码为unix下代码,de表示对应的目录级别,方便打印空格时用
{
if(dirname == NULL) {printf("%s\n","dirname can't be empty!");return;}
DIR * pdir = opendir(dirname); //先打开这个目录,返回指向此目录的目录指针
if(pdir == NULL) {printf("open dirname:%s fail\n",dirname);return;}
chdir(dirname); //进入此目录
struct dirent * sdir=NULL;
int i;
while( (sdir = readdir(dirp) )!=NULL) //读取此目录下文件
{
if(strcmp(sdir->d_name,".")==0 ||strcmp(sdir->d_name,"..")==0 ) continue;
if(sdir->d_type == DT_DIR)//目录的处理 或者 (sdir->d_type == 4)
{
for(i=0;i<de;i++) printf(" ");//根据目录和文件级别,打印出相应多的空格
printf("dir : %s/ \n",sdir->d_name);
ptdir(sdir->d_name,de+1);
}
else//文件的处理 或者 (sdir->d_type == 8)
{
for(i=0;i<de;i++) printf(" ");
printf("file : %s/ \n",sdir->d_name);
}
}
chdir("..");//退出这个目录
}
int main()
{
ptdir("ts",0);
}
7.对文件的属性操作
access函数:测试该文件是否存在、对此文件是否有读或写或执行权限,可以对权限进行或操作。若有这些权限,返回0.
Chmod、fchmod改变文件属性。Chown():改变文件属主。Mkdir()创建一个文件,一般mode_t是文件属性,可以写成8进制,如0666. rmdir()删除一个目录。
Link():创建一个硬链接。Unlink():解除硬链接。Symlink:软链接,readlink():将软链接中数据读取出来(软链接文件中数据为其指向的文件名)。
Fcntl():对文件属性设置函数,可以用此函数为文件加写、读锁。当加了写锁以后,只有本进程可以对文件进行写操作,其它进程对文件写操作是不允许的,在写的时候也不允许其它进程来读。若加了读锁,其它进程都可以读取,但不可以写。当进程1对文件加读锁后,进程2用F_GETLK得到此文件状态,得到可以读此文件,但写此文件是失败的结果。当进程1对它加写锁后,进程2对由于,它读、写锁权限,都会得到失败结果。F_GETLK F_SETLK F_SETLKW 也是用于来查询文件加的读、写锁状态的。
Mmap():在调用进程的虚拟地址空间中创建了一个新的映射,MAP_ANONYMOUS进行内存映射,若没有此标志,为文件映射。若要文件映射,映射的大小要等于文件大小(用fstat首先得到文件大小)。 unmmap() 将文件或设备映射到内存上。Mmap映射文件时,要先用ftruncate()将文件大小截取到适当的长度,截取后的文件长度要大于mmap中的第二个参数,不然会出现总线错误,此时对这个内存的操作相当于对此文件操作,对此内存读写相当于对此文件读写。
//用mmap分配内存,保存10000以内的数,并打印 (2种映射方法,内存和文件都映射了)
#include <stdio.h>
#include <sys/mman.h>
int main()
{
int fd = open("a.txt",O_RDWR);
ftruncate(fd,10000*4);//须让文件大小>=要映射大小,不然会总线错误
int * p = mmap(NULL,10000*4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,fd,SEEK_SET);//映射到文件中。
//int * p = mmap(NULL,10000*4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,0,0);//映射到物理内存上。
int i=0;
for(i=0;i<10000;i++)
p[i]=i;
for(i=0;i<10000;i++)
printf("%d\t",p[i]);
munmap(p,10000);
}
8.umask():
函数返回上一次的mode. Mkdir() rmdir()为对文件夹操作函数。
9.brk()、 sbrk()
可以字节字节的分配内存单元,当首先申请操作系统(OS)分配空间时,OS不会字节的分配,而会直接分配一页,如果在这一页内操作,不会出现错误,不然会出现段错误,在本页访问会出现越界访问,但越过这个页会出现段错误。Sbrk()参数为再分配几个字节,其返回值为未分配的新空间的首地址,sbrk()不管传入参数为多少,其返回值都为未分配空间首地址,因此常用sbrk(0)来获取未分配空间的地址。Brk()参数为分配字节的下一个单元。Brk(p),将未分配的尾地址移到分配的地址处,则收回原来分配的地址空间。Sbrk(--分配的字节数)也可以释放占用的空间。关于内存知识:使用的地址是虚拟地址(逻辑地址),要与真正的物理内存映射(对应)起来才可以使用,否则会导致段错误。 malloc 在后台是一个双向链表的数据结构,每一次调用会形成一个链表的节点。小内存:第一次给几十个页面,后面的调用会在此页面中分配。大内存:第一次调用分配所需的页面,后期调用在本段内存基础上扩充。
10.dup() dup2()
复制文件描述符,复制后的文件描述符会呈现增长趋势,因为只有一个文件,因此对此文件操作比如文件内部指针等都还是一样的,(因为是同一个文件,同一个文件的信息是一样的)。Dup2(fd,10)(10 是新的文件描述符)。也可以用函数:fcntl(fd3,F_DUPFD,0)也能得到同样效果。dup2(fd,3),如果3已经存在,先close(3),返回3. fcntl(fd,F_DUPFD,3)如果3不存在,返回3,如果存在,找到比3大的,未使用的。
11.fcntl()
是一个功能很强的函数,主要是对文件操作都可以用此函数,可以对文件加锁,解锁,得到文件锁状态,复制文件描述符,获得文件状态、参数信息等。F_GETFL F_SETFL 当fcntl中参数为 O_APPEND 时,文件指针移动文件结尾,此函数根据输入命令不同,返回值也不同。加锁之后,如果进程结束,或文件被关闭,锁会自动释放,在某个区域加读锁后,其它进程继续可以加读锁,但是不可以加写锁。在某个区域加写锁后,其它进程不可以加读锁,也不可以加写锁。谁加的锁谁解锁,F_SETLKW如果加锁失败,程序阻塞,直到加上锁为止。向文件写入结构体时,要注意结构体对齐方式。
12. open(“/dev/tty”,O_RDWR)
其中“/dev/tty”指的是键盘设备,可以用read write来对键盘进行操作。也可以根据 0(标准输入设备)1(标准输出设备)2(标准错误输出)用read write进行操作,当close(1)后,标准输出设备就不能使用,因为关闭了。如果在一个程序中被同时打开两次,这两次文件描述符不一样,但是它们的文件指针等偏移量是一样的。
13. 对文件进行操作:
当向文件中写入数据时,用结构体直接操作是不行的(读出数据是可以用结构体的),输入时先用sprintf将要写入数据格式化到buf中。
14. lseek():
当lseek移动到某处时,然后向此处写数据,会覆盖原来数据,指针的位置是(文件开头为0),write会覆盖原来数据。向文件写入数据时,一般系统会自动在文件末尾再写一个\0,具体要看具体的系统操作。Lseek()将指针移动到一个很大的位置,超过文件大小时,会形成文件空洞,并不会真正占用磁盘空间。只有移动到很大位置后,再向文件中写数据时,会真正占用磁盘空间,文件大小会真正改变。对文件读写都会改变文件指针偏移量。写文件会覆盖原来数据。每个文件描述符对应一个文件表,当open一个文件时,内核会在内存中创建一个文件表,文件表是记录文件状态对文件操作主要一些系统调用函数。lseek()文件偏移量,不会进行I/O操作,文件偏移量可以大于文件大小,这时对文件进行操作,造成文件空洞,文件空洞并不真正在磁盘上占空间。
15. getpagesize()
得到系统分配的一个页面大小(bytes),一般为4096个字节。
16.看一个程序中内存
先让程序不退出,cat /proc/程序id/mmaps。堆与栈在内存中分配空间时,假设地址从上到下,从小到大排列,则栈从下向上分配,也就是它的分配内存是倒挂的。而堆是从上向下分配的,用malloc分配内存时,malloc内部会维护一个数据结构,指示它分配的情况。Malloc如果一页中已经有,就会在这页(一般系统都会分配很多页,看具体的系统,本系统分配 33 页,只要在这33页中访问,都不会出现段错误)中继续分配空间。当对数组分配空间时,也会按页来分配。
17.数据存放空间
全局变量、有static的全局和局部变量在全局数据区,用const修饰的全局变量在代码区,用const修饰的局部变量在栈区,所以用const修饰的全局变量是不能改变其值,用const修饰的局部变量可以通过指针来改变其值。局部变量和形参在栈区。函数指针指向的为代码区,所以其指针为代码区,堆中分配的在堆中。当对 operator new和operator delete重载时,必须成对使用,或者void * operator new[](size_t sz) , operator delete [] (void * p),当对一个类构造析构时,构造时先分配内存空间,再调用构造函数,析构时先调用析构函数,再释放内存空间。
18.静态库和动态库的生成:
创建静态库:
Gcc –c 1.c 或者 gcc 1.c -c
gcc –c 2.c 或者gcc 2.c -c
ar –r lib静态库名.a 1.o 2.o
将静态库名所在的路径配置到环境变量LIBARRY_PATH下,export LIBARRY_PATH=$LIBARRY_PATH:.
连接静态库:gcc main.o –l 静态库名,如果环境变量LIBARRY_PATH没有配置静态库的路径,可直接连接: gcc main.o –l 静态库名,还可以直接连接 gcc main.o lib静态库名.a. //注:静态库名在这里只需要静态库名,不需要后缀和前面的lib
静态库定义:程序运行开始时会把程序所需要库文件直接加载进内存。
静态库生成大小为 1842. 动态库生成大小为4364.
Echo $LIBARRY_PATH
共享库:
gcc -c –fpic 1.c
gcc -c –fpic 2.c
gcc –shared 1.o 2.o -o libdltx.so lib动态库名.so
要将动态库文件所在路径配置到当前 LD_LIBRARY_PATH下:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
后面的 . 表示动态库是在当前目录下。
gcc main.c –c
gcc main.o –l 动态库名(注:动态库名后面没有so,前面也没有 lib),也可以将函数原形写到一个头文件中,在测试文件中包含这个头文件。
19.头文件编译:
gcc ***.h –c 生成一个 ***.h.gch 当编译完头文件后,要将.gch文件删除,不然后面文件使用头文件时,会优先加载.gch文件,会造成错误。
20. 环境变量:
extern char ** environ (也可以int environ,然后char **env =(char**)environ);是一个系统已经定义好的外界变量,如果 man environ,就可以看到它的信息,它里面有程序执行的环境信息,实际上int main()函数原型为 int main(int argc,char * argv[],char ** env) 此环境变量数组的最后一个数组为NULL,可以用while循环来对此环境变做循环。Setenv(“LANG”,”ENGLISH”,1);==putenv(“LANG”,”ENGLISH”);设置环境变量。得到环境变量:getenv(“LANG”);取消对环境变量设置:unsetenv(“LANG”); clearenv()清除所有环境变量值。对一个进程中的环境变量只对此进程有效,各个进程中的环境变量是独立的。
21. errno
是不个全局的错误指示标识,在头文件 #include <errno.h>, 它的值是最后一个错误码值,绝对不允许用errno来判断程序执行正确与否,因为任何一个执行语句都会改变它的值。Perror(const char *);会将程序的错误原因(错误宏)打印出来。Printf(“%m\n”)也有同样的功能。Strerror(int errnum)也是输出错误信息。Sprintf(stderr,”%s”,strerror(errno)).
22.预编译指令
#line 100 表示这个预编译下面的语句的行号指定为100.剩下程序的行号依次类推。在***.h中如果有: #if __INCLUDE_LEVEL__<10 #include <***.h> #endif 这段预编译代码是指如果自包含自己头文件嵌套数目>10,就停止编译。如果在xxx.c中有如下预编译代码: #pragma GCC dependency “yyy.h” 是指如果 yyy.h比此xxx.c还要新,程序出错。#pragma GCC poison return int hello//表示在程序中不能使用return int hello 等词,否则程序报错( _Pragma(“GCC xxx yyy”)表示不能用xxx yyy 两者同样功效 )。 #pragma pack(1) 表示结构体中对齐方式最小为 1. #warning 产生警告信息,但是程序仍能正常运行。 #error产生错误信息,程序不能正常执行。
23.动态链接库函数
在头文件#include <dlfcn.h>中,它的处理函数有dlopen()返回一个动态链接库的指针,后面的flags一般设为RTLD_LAZY,它打开成功与否用 dlerror()函数判断,使用此动态链接库中的函数的函数为: dlsym(指向库的指针,想要函数名字),返回一个与目标函数一样的指针,使用完成后用dlclose()关闭。程序运行时用 –ldl 链接。
24.在STL中,vector<int> v. sizeof(v)=12. 一个是指针、当前数据量、最前容量。malloc 和 free区别: new做的事为:分配内存空间,构造成员变量 调用构造函数 类型转换 void* 转换为 用户类型* malloc只做内存分配。 delete 调用析构函数 释放空间 free只释放空间。 malloc 在内存中维护一个数据结构 (双向链表):一次malloc会形成一个链表的节点: 分配的内存空间 上一块内存的地址 下一块内存的地址 此节点中分配的内存空间的大小 malloc系列函数分配的内存空间绝对不允许越界,一旦越界,会破坏后台内存数据结构
25.程序生成10000以内的素数,将这些数据保存到堆空间中 (brk/sbrk),程序代码如下:
#include <unistd.h>
#include <stdio.h>
int isprime(int x)//return 1 if is prime,return 0 if is not prime
{
int i;
for(i=2;i<=x/2;i++)
{
if( x%i==0 ) {return 0;}
}
return 1;
}
int main()
{
int i=1;int *p=0,*q=0;int sz=0,flag=0;
for(i=2;i<10000;i++)
{
if(isprime(i)==1)
{
p=sbrk(0);
if(flag==0) {q=p;flag=1;}
brk(p+1);*p=i;sz++;
}
}
p=sbrk(0);printf(“sz=%d\n”,sz); printf(“(p-q)=%d\n”, (p-q)); // if(p-q == sz ) the program is right
}