网易博客转载UNIX C学习(2部分)

1.fork()函数:

除去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

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值