Q: Can you insert a iptable rule with a non-existent interface ? A: Yes, You can insert any iptable rule like ip6table -A OUTPUT -o abcdefg -p ipv6-icmp --icmpv6-type router-solicitation -j DROP.

Read More

只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。信号量此处主要是用于保护线程间的共享资源,使其每次只被一个执行线程获取。

在 Linux 中,这组 API 都以 sem_ 开头。

函数原型:


#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

int sem_wait(sem_t *sem);

int sem_trywait(sem_t *sem);

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

int sem_post(sem_t *sem);

int sem_destroy(sem_t *sem);

函数说明:

sem_init 用于初始化一个未命名的信号,其值是参数 value 决定的。而二个参数是用于描述这个信号是属于进程之间共享还是线程之间共享的。

返回值在正确的情况下为 0 ,不正确的情况下为 -1,并且设置 errno 的值。

sem_wait 的意思是等待一个信号量,当信号为 0 的时候则会阻塞,当信号量大于 0 的时候则会立即返回。

sem_trywait 则是不会返回的 sem_wait 函数,当信号值为 0 的时候则会立即返回 EAGAIN 的错误码。

sem_timedwait 则可以通过第二个参数设置阻塞的时间。

 struct timespec {
               time_t tv_sec;      /* Seconds */
               long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
           };

sem_post 该函数会解锁信号,这样做就会将 sem_wait 激活并由 sem_wait 再次锁定信号。

sem_destroy 字面意思,销毁 sem_init 创建出的信号。

例子:

这里使用一个 man 手册中比较直观的例子。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>

sem_t sem;

#define handle_error(msg) \
   do { perror(msg); exit(EXIT_FAILURE); } while (0)

static void
handler(int sig)
{
   write(STDOUT_FILENO, "sem_post() from handler\n", 24);
   if (sem_post(&sem) == -1) {
       write(STDERR_FILENO, "sem_post() failed\n", 18);
       _exit(EXIT_FAILURE);
   }
}

int
main(int argc, char *argv[])
{
   struct sigaction sa;
   struct timespec ts;
   int s;

   if (argc != 3) {
       fprintf(stderr, "Usage: %s <alarm-secs> <wait-secs>\n",
               argv[0]);
       exit(EXIT_FAILURE);
   }

   if (sem_init(&sem, 0, 0) == -1)
       handle_error("sem_init");

   /* Establish SIGALRM handler; set alarm timer using argv[1] */

   sa.sa_handler = handler;
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = 0;
   if (sigaction(SIGALRM, &sa, NULL) == -1)
       handle_error("sigaction");

   alarm(atoi(argv[1]));

   /* Calculate relative interval as current time plus
      number of seconds given argv[2] */

   if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
       handle_error("clock_gettime");

   ts.tv_sec += atoi(argv[2]);

   printf("main() about to call sem_timedwait()\n");
   while ((s = sem_timedwait(&sem, &ts)) == -1 && errno == EINTR)
       continue;       /* Restart if interrupted by handler */

   /* Check what happened */

   if (s == -1) {
       if (errno == ETIMEDOUT)
           printf("sem_timedwait() timed out\n");
       else
           perror("sem_timedwait");
   } else
       printf("sem_timedwait() succeeded\n");

   exit((s == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}

Read More

进程谈通讯,线程谈同步!

术语

进程、线程、共享锁、排他锁、记录锁、原子操作…

文件锁

参考

术语介绍

LINUX锁包括“建议性锁(advisory lock)”和“强制性锁mandatory lock”。

Linux 中还引入了两种强制锁的变种形式:共享模式强制锁(share-mode mandatory lock)和租借锁(lease)。

根据细粒度锁可以分为 文件锁记录锁

记录锁根据访问形式的不同可以分为读锁和写锁。

其中读锁允许多个进程同时进行操作,称为共享锁。

写锁的主要目的是隔离文件使所写内容不被其他进程的读写干扰,以保证数据的完整性,称为排他锁。

Linux 系统的文件记录锁默认情况下是建议性的!建议性锁要求每个上锁的文件的进程都要检查是否有锁存在,并且尊重已有的锁。

共享模式锁

Linux 中还引入了两种特殊的文件锁:共享模式强制锁和租借锁。这两种文件锁可以被看成是强制锁的两种变种形式。共享模式强制锁可以用于某些私有网络文件系统,如果某个文件被加上了共享模式强制锁,那么其他进程打开该文件的时候不能与该文件的共享模式强制锁所设置的访问模式相冲突。但是由于可移植性不好,因此并不建议使用这种锁。

租借锁

采用强制锁之后,如果一个进程对某个文件拥有写锁,只要它不释放这个锁,就会导致访问该文件的其他进程全部被阻塞或不断失败重试;即使该进程只拥有读锁,也会造成后续更新该文件的进程的阻塞。为了解决这个问题,Linux 中采用了一种新型的租借锁。

当进程尝试打开一个被租借锁保护的文件时,该进程会被阻塞,同时,在一定时间内拥有该文件租借锁的进程会收到一个信号。收到信号之后,拥有该文件租借锁的进程会首先更新文件,从而保证了文件内容的一致性,接着,该进程释放这个租借锁。如果拥有租借锁的进程在一定的时间间隔内没有完成工作,内核就会自动删除这个租借锁或者将该锁进行降级,从而允许被阻塞的进程继续工作。

系统默认的这段间隔时间是 45 秒钟,定义如下:

	`137 int lease_break_time = 45;` 这个参数可以通过修改 /proc/sys/fs/lease-break-time 进行调节(当然,/proc/sys/fs/leases-enable 必须为 1 才行)。

注意

  1. 在fork 产生子进程后,一般会调用 execve 函数族在子进程中执行新的程序。如果在调用 execve 之前,

子进程中某些打开的文件描述符已经持有了文件锁,那么在执行execve 时,如果没有设置 close-on-exec 

标志,那么在新的程序中,原本打开的文件描述符依然会保持打开,原本持有的文件锁还会继续持有。

  1. 调用 dup 后,两个描述符指向了相同的文件表项,而flock 的文件锁是加在了文件表项上,因而如果对

fd0 加锁那么 fd1 就会自动持有同一把锁,释放锁时,可以使用这两个描述符中的任意一个。

线程锁(同步)

线程同步的方式:互斥量、读写锁、条件变量、自旋锁、屏障

互斥量

Linux使用互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。 使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码。

当对互斥量进行加锁时,改互斥量已经上锁。那么就会导致该进程的阻塞,也就是说同一个线程对同一个互斥量进行第二次加锁就会导致死锁的情况出现。

如果不希望阻塞发生就要使用 pthread_mutex_trylock 进行加锁,如果没锁则会上锁,如果有锁则会直接返回 0 而不会阻塞。

读写锁

读写锁也成为共享互斥锁(shared-exclusice lock)。当读锁的时候就是共享锁,当写锁的时候就是互斥锁。
概念和文件的共享锁与排他锁基本一样。

条件变量

条件变量与互斥量一起使用时,可允许线程以无竞争的方式等待特殊条件。

自旋锁

自旋锁不是通过睡眠等待而是通过轮询等待,这样的话如果在短时间内获得锁,就可以省去调度的成本。
但是在用户层线程取消调度,那么持有自旋锁的线程还是会进入睡眠。

屏障

协调多个线程并行工作的同步机制,屏障允许每个线程等待,直到所有的合作线程都到达某一个点。

线程锁(同步)

Read More

dup()用来复制参数oldfd所指的文件描述符。当复制成功是,返回最小的尚未被使用过的文件描述符,若有错误则返回-1.错误代码存入errno中返回的新文件描述符和参数oldfd指向同一个文件,这两个描述符共享同一个数据结构,共享所有的锁,读写指针和各项全现或标志位。

dup2() 功能和 dup() 完全相同,但是可以指定新的描述符。dup2与dup区别是dup2可以用参数newfd指定新文件描述符的数值。若参数newfd已经被程序使用,则系统就会将newfd所指的文件关闭,若newfd等于oldfd,则返回newfd,而不关闭newfd所指的文件。dup2所复制的文件描述符与原来的文件描述符共享各种文件状态。共享所有的锁定,读写位置和各项权限或flags等。

dup() 和 dup2() 都不共享描述符的 close-on-exec 标志。

dup3() 除了以下2点外完全和 dup2() 一致。

  1. 可以利用 flag 参数,指定新的描述符的 close-on-exec 标志。
  2. 当 newfd 等于 oldfd 的时候,会返回 EINVAL 错误。
#include <unistd.h>

 int dup(int oldfd);
 int dup2(int oldfd, int newfd);

 #define _GNU_SOURCE             /* See feature_test_macros(7) */
 #include <fcntl.h>              /* Obtain O_* constant definitions */
 #include <unistd.h>

 int dup3(int oldfd, int newfd, int flags);

close-on-exec 标志是指在调用 exec 之前会关闭这个描述符。

Read More

之前上大学时为了看而看的看了一遍韦东山老师的视频。结果两年的时间已经忘得七七八八,现在打算重新开始,认认真真的学起来。反正这里也没人看,那么正是我记录我自己学习成长的好地方……
尽量练习使用英语,只有大量的使用才会提高。

Read More